v2 / vlib / net / openssl / ssl_connection.c.v
508 lines · 459 sloc · 13.55 KB · 2cc4d27d6292f4dddeb277b24ec3d176ce1eb716
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}
20
21@[params]
22pub struct SSLConnectConfig {
23pub:
24 verify string // the path to a rootca.pem file, containing trusted CA certificate(s)
25 cert string // the path to a cert.pem file, containing client certificate(s) for the request
26 cert_key string // the path to a key.pem file, containing private keys for the client certificate(s)
27 validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid
28
29 in_memory_verification bool // if true, verify, cert, and cert_key are read from memory, not from a file
30}
31
32// new_ssl_conn instance an new SSLCon struct
33pub fn new_ssl_conn(config SSLConnectConfig) !&SSLConn {
34 $if trace_ssl ? {
35 eprintln(@METHOD)
36 }
37 mut conn := &SSLConn{
38 config: config
39 sslctx: unsafe { nil }
40 ssl: unsafe { nil }
41 handle: 0
42 }
43 conn.init() or { return err }
44 return conn
45}
46
47// Select operation
48enum Select {
49 read
50 write
51 except
52}
53
54fn ssl_timeout_deadline(timeout time.Duration) time.Time {
55 if timeout <= 0 || timeout == net.infinite_timeout {
56 return time.unix(0)
57 }
58 return time.now().add(timeout)
59}
60
61fn ssl_remaining_timeout(deadline time.Time) time.Duration {
62 if deadline.unix() == 0 {
63 return net.infinite_timeout
64 }
65 return deadline - time.now()
66}
67
68// close closes the ssl connection and does cleanup
69pub fn (mut s SSLConn) close() ! {
70 s.shutdown()!
71}
72
73// shutdown closes the ssl connection and does cleanup
74pub fn (mut s SSLConn) shutdown() ! {
75 $if trace_ssl ? {
76 eprintln(@METHOD)
77 }
78
79 if s.ssl != 0 {
80 deadline := ssl_timeout_deadline(s.duration)
81 for {
82 mut res := C.SSL_shutdown(voidptr(s.ssl))
83 if res == 1 {
84 break
85 }
86
87 err_res := ssl_error(res, s.ssl) or {
88 break // We break to free rest of resources
89 }
90 if err_res == .ssl_error_want_read {
91 s.wait_for_read(ssl_remaining_timeout(deadline))!
92 continue
93 } else if err_res == .ssl_error_want_write {
94 s.wait_for_write(ssl_remaining_timeout(deadline))!
95 continue
96 }
97 if s.ssl != 0 {
98 unsafe { C.SSL_free(voidptr(s.ssl)) }
99 }
100 if s.sslctx != 0 {
101 C.SSL_CTX_free(s.sslctx)
102 }
103 return error('net.openssl Could not connect using SSL. (${err_res}),err')
104 }
105 C.SSL_free(voidptr(s.ssl))
106 }
107 if s.sslctx != 0 {
108 C.SSL_CTX_free(s.sslctx)
109 }
110 if s.owns_socket {
111 net.shutdown(s.handle)
112 net.close(s.handle)!
113 }
114}
115
116fn (mut s SSLConn) init() ! {
117 $if trace_ssl ? {
118 eprintln(@METHOD)
119 }
120 s.sslctx = unsafe { C.SSL_CTX_new(C.SSLv23_client_method()) }
121 if s.sslctx == 0 {
122 return error('net.openssl Could not get ssl context')
123 }
124
125 if s.config.validate {
126 C.SSL_CTX_set_verify_depth(s.sslctx, 4)
127 C.SSL_CTX_set_options(s.sslctx, C.SSL_OP_NO_COMPRESSION)
128 }
129
130 s.ssl = unsafe { &C.SSL(C.SSL_new(s.sslctx)) }
131 if s.ssl == 0 {
132 return error('net.openssl Could not create OpenSSL instance')
133 }
134
135 mut res := 0
136
137 if s.config.validate {
138 mut verify := s.config.verify
139 mut cert := s.config.cert
140 mut cert_key := s.config.cert_key
141 if s.config.in_memory_verification {
142 now := time.now().unix().str()
143 verify = os.temp_dir() + '/v_verify' + now
144 cert = os.temp_dir() + '/v_cert' + now
145 cert_key = os.temp_dir() + '/v_cert_key' + now
146 if s.config.verify != '' {
147 os.write_file(verify, s.config.verify)!
148 }
149 if s.config.cert != '' {
150 os.write_file(cert, s.config.cert)!
151 }
152 if s.config.cert_key != '' {
153 os.write_file(cert_key, s.config.cert_key)!
154 }
155 }
156 if s.config.verify != '' {
157 res = C.SSL_CTX_load_verify_locations(voidptr(s.sslctx), &char(verify.str), 0)
158 if s.config.validate && res != 1 {
159 return error('net.openssl SSLConn.init, SSL_CTX_load_verify_locations failed')
160 }
161 }
162 if s.config.cert != '' {
163 res = C.SSL_CTX_use_certificate_file(voidptr(s.sslctx), &char(cert.str),
164 C.SSL_FILETYPE_PEM)
165 if s.config.validate && res != 1 {
166 return error('net.openssl SSLConn.init, SSL_CTX_use_certificate_file failed, res: ${res}')
167 }
168 }
169 if s.config.cert_key != '' {
170 res = C.SSL_CTX_use_PrivateKey_file(voidptr(s.sslctx), &char(cert_key.str),
171 C.SSL_FILETYPE_PEM)
172 if s.config.validate && res != 1 {
173 return error('net.openssl SSLConn.init, SSL_CTX_use_PrivateKey_file failed, res: ${res}')
174 }
175 }
176
177 preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4'
178 res = C.SSL_set_cipher_list(voidptr(s.ssl), &char(preferred_ciphers.str))
179 if s.config.validate && res != 1 {
180 println('net.openssl: set cipher failed')
181 }
182 }
183}
184
185// connect to server using OpenSSL
186pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ! {
187 $if trace_ssl ? {
188 eprintln('${@METHOD} hostname: ${hostname}')
189 }
190 s.handle = tcp_conn.sock.handle
191 s.duration = tcp_conn.read_timeout()
192 mut res := C.SSL_set_tlsext_host_name(voidptr(s.ssl), voidptr(hostname.str))
193 if res != 1 {
194 return error('net.openssl SSLConn.connect, could not set host name')
195 }
196 if C.SSL_set_fd(voidptr(s.ssl), tcp_conn.sock.handle) != 1 {
197 return error('net.openssl SSLConn.connect, could not assign ssl to socket.')
198 }
199 s.complete_connect()!
200}
201
202// dial opens an ssl connection on hostname:port
203pub fn (mut s SSLConn) dial(hostname string, port int) ! {
204 $if trace_ssl ? {
205 eprintln('${@METHOD} hostname: ${hostname} | port: ${port}')
206 }
207 mut tcp_conn := net.dial_tcp('${hostname}:${port}') or { return err }
208 mut connected := false
209 defer {
210 if !connected {
211 tcp_conn.close() or {}
212 if s.ssl != 0 {
213 unsafe { C.SSL_free(voidptr(s.ssl)) }
214 s.ssl = unsafe { nil }
215 }
216 if s.sslctx != 0 {
217 C.SSL_CTX_free(s.sslctx)
218 s.sslctx = unsafe { nil }
219 }
220 s.handle = 0
221 s.owns_socket = false
222 }
223 }
224 s.owns_socket = true
225 s.connect(mut tcp_conn, hostname) or { return err }
226 connected = true
227}
228
229fn (mut s SSLConn) complete_connect() ! {
230 $if trace_ssl ? {
231 eprintln(@METHOD)
232 }
233
234 deadline := ssl_timeout_deadline(s.duration)
235 for {
236 mut res := C.SSL_connect(voidptr(s.ssl))
237 if res == 1 {
238 break
239 }
240
241 err_res := ssl_error(res, s.ssl)!
242 if err_res == .ssl_error_want_read {
243 s.wait_for_read(ssl_remaining_timeout(deadline))!
244 continue
245 }
246 if err_res == .ssl_error_want_write {
247 s.wait_for_write(ssl_remaining_timeout(deadline))!
248 continue
249 }
250 return error('net.openssl SSLConn.complete_connect, could not connect using SSL. (${err_res}),err')
251 }
252
253 if s.config.validate {
254 mut pcert := &C.X509(unsafe { nil })
255 for {
256 mut res := C.SSL_do_handshake(voidptr(s.ssl))
257 if res == 1 {
258 break
259 }
260
261 err_res := ssl_error(res, s.ssl)!
262 if err_res == .ssl_error_want_read {
263 s.wait_for_read(ssl_remaining_timeout(deadline))!
264 continue
265 } else if err_res == .ssl_error_want_write {
266 s.wait_for_write(ssl_remaining_timeout(deadline))!
267 continue
268 }
269 return error('net.openssl SSLConn.complete_connect, could not validate SSL certificate. (${err_res}),err')
270 }
271 pcert = C.v_net_openssl_get1_peer_certificate(s.ssl)
272 defer {
273 if pcert != 0 {
274 C.X509_free(pcert)
275 }
276 }
277 res := C.SSL_get_verify_result(voidptr(s.ssl))
278 if res != C.X509_V_OK {
279 return error('net.openssl SSLConn.complete_connect, failed SSL handshake (OpenSSL SSL_get_verify_result = ${res})')
280 }
281 }
282}
283
284// read_timeout returns the current SSL read timeout.
285pub fn (s &SSLConn) read_timeout() time.Duration {
286 return s.duration
287}
288
289// set_read_timeout sets the read timeout used for subsequent SSL reads.
290// A value of 0, or net.infinite_timeout means "wait forever".
291pub fn (mut s SSLConn) set_read_timeout(timeout time.Duration) {
292 s.duration = timeout
293}
294
295// addr retrieves the local ip address and port number for this connection
296pub fn (s &SSLConn) addr() !net.Addr {
297 return net.addr_from_socket_handle(s.handle)
298}
299
300// peer_addr retrieves the ip address and port number used by the peer
301pub fn (s &SSLConn) peer_addr() !net.Addr {
302 return net.peer_addr_from_socket_handle(s.handle)
303}
304
305pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &u8, len int) !int {
306 mut res := 0
307 $if trace_ssl ? {
308 defer(fn) {
309 if len > 0 {
310 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) }}`')
311 }
312 }
313 }
314
315 deadline := ssl_timeout_deadline(s.duration)
316 // s.wait_for_read(deadline - time.now())!
317 for {
318 res = C.SSL_read(voidptr(s.ssl), buf_ptr, len)
319 if res > 0 {
320 return res
321 } else if res == 0 {
322 $if trace_ssl ? {
323 eprintln('${@METHOD} ---> res: io.Eof')
324 }
325 return io.Eof{}
326 } else {
327 err_res := ssl_error(res, s.ssl)!
328 match err_res {
329 .ssl_error_want_read {
330 s.wait_for_read(ssl_remaining_timeout(deadline)) or {
331 $if trace_ssl ? {
332 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_read')
333 }
334 return err
335 }
336 }
337 .ssl_error_want_write {
338 s.wait_for_write(ssl_remaining_timeout(deadline)) or {
339 $if trace_ssl ? {
340 eprintln('${@METHOD} ---> res: ${err} .ssl_error_want_write')
341 }
342 return err
343 }
344 }
345 .ssl_error_zero_return {
346 $if trace_ssl ? {
347 eprintln('${@METHOD} ---> res: 0 .ssl_error_zero_return')
348 }
349 return 0
350 }
351 else {
352 $if trace_ssl ? {
353 eprintln('${@METHOD} ---> res: could not read, err_res: ${err_res}')
354 }
355 return error('net.openssl SSLConn.socket_read_into_ptr, could not read. (${err_res})')
356 }
357 }
358 }
359 }
360
361 // Dead code, to satisfy the compiler
362 return error('net.openssl SSLConn.socket_read_into_ptr, unknown error')
363}
364
365pub fn (mut s SSLConn) read(mut buffer []u8) !int {
366 $if trace_ssl ? {
367 eprintln('${@METHOD} buffer.len: ${buffer.len}')
368 }
369 return s.socket_read_into_ptr(&u8(buffer.data), buffer.len)
370}
371
372// write_ptr writes `len` bytes from `bytes` to the ssl connection
373pub fn (mut s SSLConn) write_ptr(bytes &u8, len int) !int {
374 mut total_sent := 0
375 $if trace_ssl ? {
376 defer(fn) {
377 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-=-=-=-')
378 }
379 }
380
381 deadline := ssl_timeout_deadline(s.duration)
382 unsafe {
383 mut ptr_base := bytes
384 for total_sent < len {
385 ptr := ptr_base + total_sent
386 remaining := len - total_sent
387 mut sent := C.SSL_write(voidptr(s.ssl), ptr, remaining)
388 if sent <= 0 {
389 err_res := ssl_error(sent, s.ssl)!
390 if err_res == .ssl_error_want_read {
391 s.wait_for_read(ssl_remaining_timeout(deadline))!
392 continue
393 } else if err_res == .ssl_error_want_write {
394 s.wait_for_write(ssl_remaining_timeout(deadline))!
395 continue
396 } else if err_res == .ssl_error_zero_return {
397 $if trace_ssl ? {
398 eprintln('${@METHOD} ---> res: ssl write on closed connection .ssl_error_zero_return')
399 }
400 return error('net.openssl SSLConn.write_ptr, ssl write on closed connection') // TODO: error_with_code close
401 }
402 $if trace_ssl ? {
403 eprintln('${@METHOD} ---> res: could not write SSL, err_res: ${err_res}')
404 }
405 return error_with_code('net.openssl SSLConn.write_ptr, could not write. (${err_res}),err',
406 int(err_res))
407 }
408 total_sent += sent
409 }
410 }
411 return total_sent
412}
413
414// write writes data from `bytes` to the ssl connection
415pub fn (mut s SSLConn) write(bytes []u8) !int {
416 return s.write_ptr(&u8(bytes.data), bytes.len)!
417}
418
419// write_string writes a string to the ssl connection
420pub fn (mut s SSLConn) write_string(str string) !int {
421 $if trace_ssl ? {
422 eprintln('${@METHOD} str: ${str}')
423 }
424 return s.write_ptr(str.str, str.len)
425}
426
427// Select waits for an io operation (specified by parameter `test`) to be available
428fn select(handle int, test Select, timeout time.Duration) !bool {
429 $if trace_ssl ? {
430 eprintln('${@METHOD} handle: ${handle}, timeout: ${timeout}')
431 }
432 set := C.fd_set{}
433 C.FD_ZERO(&set)
434 C.FD_SET(handle, &set)
435
436 is_infinite := timeout <= 0 || timeout == net.infinite_timeout
437 deadline := ssl_timeout_deadline(timeout)
438 mut remaining_time := if is_infinite { i64(0) } else { timeout.milliseconds() }
439 for is_infinite || remaining_time > 0 {
440 seconds := remaining_time / 1000
441 microseconds := (remaining_time % 1000) * 1000
442
443 tt := C.timeval{
444 tv_sec: u64(seconds)
445 tv_usec: u64(microseconds)
446 }
447 timeval_timeout := if is_infinite {
448 &C.timeval(unsafe { nil })
449 } else {
450 &tt
451 }
452
453 mut res := -1
454 match test {
455 .read {
456 res = net.socket_error(C.select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout))!
457 }
458 .write {
459 res = net.socket_error(C.select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout))!
460 }
461 .except {
462 res = net.socket_error(C.select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout))!
463 }
464 }
465
466 if res < 0 {
467 if C.errno == C.EINTR {
468 // errno is 4, Spurious wakeup from signal, keep waiting
469 if !is_infinite {
470 remaining_time = ssl_remaining_timeout(deadline).milliseconds()
471 }
472 continue
473 }
474 cerr := C.errno
475 return error_with_code('net.openssl select, failed: ${res}', cerr)
476 } else if res == 0 {
477 return net.err_timed_out
478 }
479
480 res = C.FD_ISSET(handle, &set)
481 $if trace_ssl ? {
482 eprintln('${@METHOD} ---> res: ${res}')
483 }
484 return res != 0
485 }
486
487 return net.err_timed_out
488}
489
490// wait_for wraps the common wait code
491fn wait_for(handle int, what Select, timeout time.Duration) ! {
492 ready := select(handle, what, timeout)!
493 if ready {
494 return
495 }
496
497 return net.err_timed_out
498}
499
500// wait_for_write waits for a write io operation to be available
501fn (mut s SSLConn) wait_for_write(timeout time.Duration) ! {
502 return wait_for(s.handle, .write, timeout)
503}
504
505// wait_for_read waits for a read io operation to be available
506fn (mut s SSLConn) wait_for_read(timeout time.Duration) ! {
507 return wait_for(s.handle, .read, timeout)
508}
509