v / vlib / net / openssl / ssl_connection.c.v
557 lines · 505 sloc · 15.78 KB · 75789c6f9989ff8de76988c5d312979ca5e27853
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 != 0 {
102 deadline := ssl_timeout_deadline(s.duration)
103 for {
104 mut res := C.SSL_shutdown(voidptr(s.ssl))
105 if res == 1 {
106 break
107 }
108
109 err_res := ssl_error(res, s.ssl) or {
110 break // We break to free rest of resources
111 }
112 if err_res == .ssl_error_want_read {
113 s.wait_for_read(ssl_remaining_timeout(deadline))!
114 continue
115 } else if err_res == .ssl_error_want_write {
116 s.wait_for_write(ssl_remaining_timeout(deadline))!
117 continue
118 }
119 if s.ssl != 0 {
120 unsafe { C.SSL_free(voidptr(s.ssl)) }
121 }
122 if s.sslctx != 0 {
123 C.SSL_CTX_free(s.sslctx)
124 }
125 return error('net.openssl Could not connect using SSL. (${err_res}),err')
126 }
127 C.SSL_free(voidptr(s.ssl))
128 }
129 if s.sslctx != 0 {
130 C.SSL_CTX_free(s.sslctx)
131 }
132 if s.owns_socket {
133 net.shutdown(s.handle)
134 net.close(s.handle)!
135 }
136}
137
138fn (mut s SSLConn) init() ! {
139 $if trace_ssl ? {
140 eprintln(@METHOD)
141 }
142 s.sslctx = unsafe { C.SSL_CTX_new(C.SSLv23_client_method()) }
143 if s.sslctx == 0 {
144 return error('net.openssl Could not get ssl context')
145 }
146
147 if s.config.validate {
148 C.SSL_CTX_set_verify_depth(s.sslctx, 4)
149 C.SSL_CTX_set_options(s.sslctx, C.SSL_OP_NO_COMPRESSION)
150 }
151
152 s.ssl = unsafe { &C.SSL(C.SSL_new(s.sslctx)) }
153 if s.ssl == 0 {
154 return error('net.openssl Could not create OpenSSL instance')
155 }
156
157 mut res := 0
158
159 // Advertise ALPN protocols (e.g. ['h2', 'http/1.1']) when requested.
160 // OpenSSL expects the length-prefixed wire format: each protocol is a
161 // single length byte followed by that many bytes of protocol name.
162 if s.config.alpn_protocols.len > 0 {
163 mut wire := []u8{cap: 64}
164 for proto in s.config.alpn_protocols {
165 if proto.len == 0 || proto.len > 255 {
166 return error('net.openssl SSLConn.init, invalid ALPN protocol "${proto}"')
167 }
168 wire << u8(proto.len)
169 wire << proto.bytes()
170 }
171 // Returns 0 on success (opposite of most OpenSSL calls); non-zero also
172 // means ALPN is unavailable on OpenSSL versions older than 1.0.2.
173 if C.v_net_openssl_set_alpn_protos(voidptr(s.ssl), wire.data, u32(wire.len)) != 0 {
174 return error('net.openssl SSLConn.init, failed to set ALPN protocols (requires OpenSSL >= 1.0.2)')
175 }
176 }
177
178 if s.config.validate {
179 mut verify := s.config.verify
180 mut cert := s.config.cert
181 mut cert_key := s.config.cert_key
182 if s.config.in_memory_verification {
183 now := time.now().unix().str()
184 verify = os.temp_dir() + '/v_verify' + now
185 cert = os.temp_dir() + '/v_cert' + now
186 cert_key = os.temp_dir() + '/v_cert_key' + now
187 if s.config.verify != '' {
188 os.write_file(verify, s.config.verify)!
189 }
190 if s.config.cert != '' {
191 os.write_file(cert, s.config.cert)!
192 }
193 if s.config.cert_key != '' {
194 os.write_file(cert_key, s.config.cert_key)!
195 }
196 }
197 if s.config.verify != '' {
198 res = C.SSL_CTX_load_verify_locations(voidptr(s.sslctx), &char(verify.str), 0)
199 if s.config.validate && res != 1 {
200 return error('net.openssl SSLConn.init, SSL_CTX_load_verify_locations failed')
201 }
202 }
203 if s.config.cert != '' {
204 res = C.SSL_CTX_use_certificate_file(voidptr(s.sslctx), &char(cert.str),
205 C.SSL_FILETYPE_PEM)
206 if s.config.validate && res != 1 {
207 return error('net.openssl SSLConn.init, SSL_CTX_use_certificate_file failed, res: ${res}')
208 }
209 }
210 if s.config.cert_key != '' {
211 res = C.SSL_CTX_use_PrivateKey_file(voidptr(s.sslctx), &char(cert_key.str),
212 C.SSL_FILETYPE_PEM)
213 if s.config.validate && res != 1 {
214 return error('net.openssl SSLConn.init, SSL_CTX_use_PrivateKey_file failed, res: ${res}')
215 }
216 }
217
218 preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4'
219 res = C.SSL_set_cipher_list(voidptr(s.ssl), &char(preferred_ciphers.str))
220 if s.config.validate && res != 1 {
221 println('net.openssl: set cipher failed')
222 }
223 }
224}
225
226// connect to server using OpenSSL
227pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ! {
228 $if trace_ssl ? {
229 eprintln('${@METHOD} hostname: ${hostname}')
230 }
231 s.handle = tcp_conn.sock.handle
232 s.duration = tcp_conn.read_timeout()
233 mut res := C.SSL_set_tlsext_host_name(voidptr(s.ssl), voidptr(hostname.str))
234 if res != 1 {
235 return error('net.openssl SSLConn.connect, could not set host name')
236 }
237 if C.SSL_set_fd(voidptr(s.ssl), tcp_conn.sock.handle) != 1 {
238 return error('net.openssl SSLConn.connect, could not assign ssl to socket.')
239 }
240 s.complete_connect()!
241}
242
243// dial opens an ssl connection on hostname:port
244pub fn (mut s SSLConn) dial(hostname string, port int) ! {
245 $if trace_ssl ? {
246 eprintln('${@METHOD} hostname: ${hostname} | port: ${port}')
247 }
248 mut tcp_conn := net.dial_tcp('${hostname}:${port}') or { return err }
249 mut connected := false
250 defer {
251 if !connected {
252 tcp_conn.close() or {}
253 if s.ssl != 0 {
254 unsafe { C.SSL_free(voidptr(s.ssl)) }
255 s.ssl = unsafe { nil }
256 }
257 if s.sslctx != 0 {
258 C.SSL_CTX_free(s.sslctx)
259 s.sslctx = unsafe { nil }
260 }
261 s.handle = 0
262 s.owns_socket = false
263 }
264 }
265 s.owns_socket = true
266 s.connect(mut tcp_conn, hostname) or { return err }
267 connected = true
268}
269
270fn (mut s SSLConn) complete_connect() ! {
271 $if trace_ssl ? {
272 eprintln(@METHOD)
273 }
274
275 deadline := ssl_timeout_deadline(s.duration)
276 for {
277 mut res := C.SSL_connect(voidptr(s.ssl))
278 if res == 1 {
279 break
280 }
281
282 err_res := ssl_error(res, s.ssl)!
283 if err_res == .ssl_error_want_read {
284 s.wait_for_read(ssl_remaining_timeout(deadline))!
285 continue
286 }
287 if err_res == .ssl_error_want_write {
288 s.wait_for_write(ssl_remaining_timeout(deadline))!
289 continue
290 }
291 return error('net.openssl SSLConn.complete_connect, could not connect using SSL. (${err_res}),err')
292 }
293
294 if s.config.validate {
295 mut pcert := &C.X509(unsafe { nil })
296 for {
297 mut res := C.SSL_do_handshake(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 } else if err_res == .ssl_error_want_write {
307 s.wait_for_write(ssl_remaining_timeout(deadline))!
308 continue
309 }
310 return error('net.openssl SSLConn.complete_connect, could not validate SSL certificate. (${err_res}),err')
311 }
312 pcert = C.v_net_openssl_get1_peer_certificate(s.ssl)
313 defer {
314 if pcert != 0 {
315 C.X509_free(pcert)
316 }
317 }
318 res := C.SSL_get_verify_result(voidptr(s.ssl))
319 if res != C.X509_V_OK {
320 return error('net.openssl SSLConn.complete_connect, failed SSL handshake (OpenSSL SSL_get_verify_result = ${res})')
321 }
322 }
323}
324
325// read_timeout returns the current SSL read timeout.
326pub fn (s &SSLConn) read_timeout() time.Duration {
327 return s.duration
328}
329
330// set_read_timeout sets the read timeout used for subsequent SSL reads.
331// A value of 0, or net.infinite_timeout means "wait forever".
332pub fn (mut s SSLConn) set_read_timeout(timeout time.Duration) {
333 s.duration = timeout
334}
335
336// addr retrieves the local ip address and port number for this connection
337pub fn (s &SSLConn) addr() !net.Addr {
338 return net.addr_from_socket_handle(s.handle)
339}
340
341// peer_addr retrieves the ip address and port number used by the peer
342pub fn (s &SSLConn) peer_addr() !net.Addr {
343 return net.peer_addr_from_socket_handle(s.handle)
344}
345
346pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int {
347 mut res := 0
348 $if trace_ssl ? {
349 defer(fn) {
350 if len > 0 {
351 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) }}`')
352 }
353 }
354 }
355
356 deadline := ssl_timeout_deadline(s.duration)
357 // s.wait_for_read(deadline - time.now())!
358 for {
359 res = C.SSL_read(voidptr(s.ssl), buf_ptr, len)
360 if res > 0 {
361 return res
362 } else if res == 0 {
363 $if trace_ssl ? {
364 eprintln('${@METHOD} ---> res: io.Eof')
365 }
366 return io.Eof{}
367 } else {
368 err_res := ssl_error(res, s.ssl)!
369 match err_res {
370 .ssl_error_want_read {
371 s.wait_for_read(ssl_remaining_timeout(deadline)) or {
372 $if trace_ssl ? {
373 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_read')
374 }
375 return err
376 }
377 }
378 .ssl_error_want_write {
379 s.wait_for_write(ssl_remaining_timeout(deadline)) or {
380 $if trace_ssl ? {
381 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_write')
382 }
383 return err
384 }
385 }
386 .ssl_error_zero_return {
387 $if trace_ssl ? {
388 eprintln('${@METHOD} ---> res: 0 .ssl_error_zero_return')
389 }
390 return 0
391 }
392 else {
393 $if trace_ssl ? {
394 eprintln('${@METHOD} ---> res: could not read, err_res: ${err_res}')
395 }
396 return error('net.openssl SSLConn.socket_read_into_ptr, could not read. (${err_res})')
397 }
398 }
399 }
400 }
401
402 // Dead code, to satisfy the compiler
403 return error('net.openssl SSLConn.socket_read_into_ptr, unknown error')
404}
405
406pub fn (mut s SSLConn) read(mut buffer []u8) !int {
407 $if trace_ssl ? {
408 eprintln('${@METHOD} buffer.len: ${buffer.len}')
409 }
410 return s.socket_read_into_ptr(&u8(buffer.data), buffer.len)
411}
412
413// write_ptr writes `len` bytes from `bytes` to the ssl connection
414pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int {
415 mut total_sent := 0
416 $if trace_ssl ? {
417 defer(fn) {
418 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-=-=-=-')
419 }
420 }
421
422 s.last_write_sent = 0
423 deadline := ssl_timeout_deadline(s.duration)
424 unsafe {
425 mut ptr_base := bytes
426 for total_sent < len {
427 ptr := ptr_base + total_sent
428 remaining := len - total_sent
429 mut sent := C.SSL_write(voidptr(s.ssl), ptr, remaining)
430 if sent <= 0 {
431 // SSL_write did not fully complete: OpenSSL may already have
432 // flushed one or more complete records (which the peer can
433 // decrypt and act on) before returning a retryable error, so the
434 // sent count is no longer provable. Mark it indeterminate; a
435 // later full success below resets it to the exact length.
436 s.last_write_sent = -1
437 err_res := ssl_error(sent, s.ssl)!
438 if err_res == .ssl_error_want_read {
439 s.wait_for_read(ssl_remaining_timeout(deadline))!
440 continue
441 } else if err_res == .ssl_error_want_write {
442 s.wait_for_write(ssl_remaining_timeout(deadline))!
443 continue
444 } else if err_res == .ssl_error_zero_return {
445 $if trace_ssl ? {
446 eprintln('${@METHOD} ---> res: ssl write on closed connection .ssl_error_zero_return')
447 }
448 return error('net.openssl SSLConn.write_ptr, ssl write on closed connection') // TODO: error_with_code close
449 }
450 $if trace_ssl ? {
451 eprintln('${@METHOD} ---> res: could not write SSL, err_res: ${err_res}')
452 }
453 return error_with_code('net.openssl SSLConn.write_ptr, could not write. (${err_res}),err',
454 int(err_res))
455 }
456 total_sent += sent
457 s.last_write_sent = total_sent
458 }
459 }
460 return total_sent
461}
462
463// write writes data from `bytes` to the ssl connection
464pub fn (mut s SSLConn) write(bytes []u8) !int {
465 return s.write_ptr(&u8(bytes.data), bytes.len)!
466}
467
468// write_string writes a string to the ssl connection
469pub fn (mut s SSLConn) write_string(str string) !int {
470 $if trace_ssl ? {
471 eprintln('${@METHOD} str: ${str}')
472 }
473 return s.write_ptr(str.str, str.len)
474}
475
476// Select waits for an io operation (specified by parameter `test`) to be available
477fn select(handle int, test Select, timeout time.Duration) !bool {
478 $if trace_ssl ? {
479 eprintln('${@METHOD} handle: ${handle}, timeout: ${timeout}')
480 }
481 set := C.fd_set{}
482 C.FD_ZERO(&set)
483 C.FD_SET(handle, &set)
484
485 is_infinite := timeout <= 0 || timeout == net.infinite_timeout
486 deadline := ssl_timeout_deadline(timeout)
487 mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() }
488 for is_infinite || remaining_time > 0 {
489 seconds := remaining_time / 1000
490 microseconds := (remaining_time % 1000) * 1000
491
492 tt := C.timeval{
493 tv_sec: u64(seconds)
494 tv_usec: u64(microseconds)
495 }
496 timeval_timeout := if is_infinite {
497 &C.timeval(unsafe { nil })
498 } else {
499 &tt
500 }
501
502 mut res := -1
503 match test {
504 .read {
505 res = net.socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))!
506 }
507 .write {
508 res = net.socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))!
509 }
510 .except {
511 res = net.socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))!
512 }
513 }
514
515 if res < 0 {
516 if C.errno == C.EINTR {
517 // errno is 4, Spurious wakeup from signal, keep waiting
518 if !is_infinite {
519 remaining_time = ssl_remaining_timeout(deadline).milliseconds()
520 }
521 continue
522 }
523 cerr := C.errno
524 return error_with_code('net.openssl select, failed: ${res}', cerr)
525 } else if res == 0 {
526 return net.err_timed_out
527 }
528
529 res = C.FD_ISSET(handle, &set)
530 $if trace_ssl ? {
531 eprintln('${@METHOD} ---> res: ${res}')
532 }
533 return res != 0
534 }
535
536 return net.err_timed_out
537}
538
539// wait_for wraps the common wait code
540fn wait_for(handle int, what Select, timeout time.Duration) ! {
541 ready := select(handle, what, timeout)!
542 if ready {
543 return
544 }
545
546 return net.err_timed_out
547}
548
549// wait_for_write waits for a write io operation to be available
550fn (mut s SSLConn) wait_for_write(timeout time.Duration) ! {
551 return wait_for(s.handle, .write, timeout)
552}
553
554// wait_for_read waits for a read io operation to be available
555fn (mut s SSLConn) wait_for_read(timeout time.Duration) ! {
556 return wait_for(s.handle, .read, timeout)
557}
558