From f5c4671bfbad96bf346bd7e9a21fc4317b4959df Mon Sep 17 00:00:00 2001 From: Indrajith K L Date: Sat, 3 Dec 2022 17:00:20 +0530 Subject: Adds most of the tools --- v_windows/v/vlib/net/openssl/c.v | 120 ++++++++++++ v_windows/v/vlib/net/openssl/openssl.v | 32 +++ v_windows/v/vlib/net/openssl/ssl_connection.v | 268 ++++++++++++++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 v_windows/v/vlib/net/openssl/c.v create mode 100644 v_windows/v/vlib/net/openssl/openssl.v create mode 100644 v_windows/v/vlib/net/openssl/ssl_connection.v (limited to 'v_windows/v/vlib/net/openssl') diff --git a/v_windows/v/vlib/net/openssl/c.v b/v_windows/v/vlib/net/openssl/c.v new file mode 100644 index 0000000..dedba2a --- /dev/null +++ b/v_windows/v/vlib/net/openssl/c.v @@ -0,0 +1,120 @@ +module openssl + +// On Linux, prefer a localy built openssl, because it is +// much more likely for it to be newer, than the system +// openssl from libssl-dev. If there is no local openssl, +// the next flag is harmless, since it will still use the +// (older) system openssl. +#flag linux -I/usr/local/include/openssl -L/usr/local/lib +#flag windows -l libssl -l libcrypto +#flag -lssl -lcrypto +#flag linux -ldl -lpthread +// MacPorts +#flag darwin -I/opt/local/include +#flag darwin -L/opt/local/lib +// Brew +#flag darwin -I/usr/local/opt/openssl/include +#flag darwin -L/usr/local/opt/openssl/lib +// Brew arm64 +#flag darwin -I /opt/homebrew/opt/openssl/include +#flag darwin -L /opt/homebrew/opt/openssl/lib +// +#include # Please install OpenSSL development headers +#include +#include + +pub struct C.SSL { +} + +pub struct SSL_CTX { +} + +pub struct SSL { +} + +pub struct SSL_METHOD { +} + +pub struct OPENSSL_INIT_SETTINGS { +} + +fn C.BIO_new_ssl_connect(ctx &C.SSL_CTX) &C.BIO + +fn C.BIO_set_conn_hostname(b &C.BIO, name &char) int + +// there are actually 2 macros for BIO_get_ssl +// fn C.BIO_get_ssl(bp &C.BIO, ssl charptr, c int) +// fn C.BIO_get_ssl(bp &C.BIO, sslp charptr) +fn C.BIO_get_ssl(bp &C.BIO, vargs ...voidptr) + +fn C.BIO_do_connect(b &C.BIO) int + +fn C.BIO_do_handshake(b &C.BIO) int + +fn C.BIO_puts(b &C.BIO, buf &char) + +fn C.BIO_read(b &C.BIO, buf voidptr, len int) int + +fn C.BIO_free_all(a &C.BIO) + +fn C.SSL_CTX_new(method &C.SSL_METHOD) &C.SSL_CTX + +fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options int) + +fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth int) + +fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, ca_file &char, ca_path &char) int + +fn C.SSL_CTX_free(ctx &C.SSL_CTX) + +fn C.SSL_new(&C.SSL_CTX) &C.SSL + +fn C.SSL_set_fd(ssl &C.SSL, fd int) int + +fn C.SSL_connect(&C.SSL) int + +fn C.SSL_set_cipher_list(ctx &SSL, str &char) int + +fn C.SSL_get_peer_certificate(ssl &SSL) &C.X509 + +fn C.ERR_clear_error() + +fn C.SSL_get_error(ssl &C.SSL, ret int) int + +fn C.SSL_get_verify_result(ssl &SSL) int + +fn C.SSL_set_tlsext_host_name(s &SSL, name &char) int + +fn C.SSL_shutdown(&C.SSL) int + +fn C.SSL_free(&C.SSL) + +fn C.SSL_write(ssl &C.SSL, buf voidptr, buflen int) int + +fn C.SSL_read(ssl &C.SSL, buf voidptr, buflen int) int + +fn C.SSL_load_error_strings() + +fn C.SSL_library_init() int + +fn C.SSLv23_client_method() &C.SSL_METHOD + +fn C.TLS_method() voidptr + +fn C.TLSv1_2_method() voidptr + +fn C.OPENSSL_init_ssl(opts u64, settings &OPENSSL_INIT_SETTINGS) int + +fn init() { + $if ssl_pre_1_1_version ? { + // OPENSSL_VERSION_NUMBER < 0x10100000L + C.SSL_load_error_strings() + C.SSL_library_init() + } $else { + C.OPENSSL_init_ssl(C.OPENSSL_INIT_LOAD_SSL_STRINGS, 0) + } +} + +pub const ( + is_used = 1 +) diff --git a/v_windows/v/vlib/net/openssl/openssl.v b/v_windows/v/vlib/net/openssl/openssl.v new file mode 100644 index 0000000..ffcabf5 --- /dev/null +++ b/v_windows/v/vlib/net/openssl/openssl.v @@ -0,0 +1,32 @@ +module openssl + +// ssl_error returns non error ssl code or error if unrecoverable and we should panic +pub fn ssl_error(ret int, ssl voidptr) ?SSLError { + res := C.SSL_get_error(ssl, ret) + match SSLError(res) { + .ssl_error_syscall { + return error_with_code('unrecoverable syscall ($res)', res) + } + .ssl_error_ssl { + return error_with_code('unrecoverable ssl protocol error ($res)', res) + } + else { + return SSLError(res) + } + } +} + +pub enum SSLError { + ssl_error_none = 0 // SSL_ERROR_NONE + ssl_error_ssl = 1 // SSL_ERROR_SSL + ssl_error_want_read = 2 // SSL_ERROR_WANT_READ + ssl_error_want_write = 3 // SSL_ERROR_WANT_WRITE + ssl_error_want_x509_lookup = 4 // SSL_ERROR_WANT_X509_LOOKUP + ssl_error_syscall = 5 // SSL_ERROR_SYSCALL + ssl_error_zero_return = 6 // SSL_ERROR_ZERO_RETURN + ssl_error_want_connect = 7 // SSL_ERROR_WANT_CONNECT + ssl_error_want_accept = 8 // SSL_ERROR_WANT_ACCEPT + ssl_error_want_async = 9 // SSL_ERROR_WANT_ASYNC + ssl_error_want_async_job = 10 // SSL_ERROR_WANT_ASYNC_JOB + ssl_error_want_early = 11 // SSL_ERROR_WANT_EARLY +} diff --git a/v_windows/v/vlib/net/openssl/ssl_connection.v b/v_windows/v/vlib/net/openssl/ssl_connection.v new file mode 100644 index 0000000..58f47f6 --- /dev/null +++ b/v_windows/v/vlib/net/openssl/ssl_connection.v @@ -0,0 +1,268 @@ +module openssl + +import net +import time + +// SSLConn is the current connection +pub struct SSLConn { +mut: + sslctx &C.SSL_CTX + ssl &C.SSL + handle int + duration time.Duration +} + +// new_ssl_conn instance an new SSLCon struct +pub fn new_ssl_conn() &SSLConn { + return &SSLConn{ + sslctx: 0 + ssl: 0 + handle: 0 + } +} + +// Select operation +enum Select { + read + write + except +} + +// shutdown closes the ssl connection and do clean up +pub fn (mut s SSLConn) shutdown() ? { + if s.ssl != 0 { + mut res := 0 + for { + res = C.SSL_shutdown(voidptr(s.ssl)) + if res < 0 { + err_res := ssl_error(res, s.ssl) or { + break // We break to free rest of resources + } + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else { + unsafe { C.SSL_free(voidptr(s.ssl)) } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } + return error('unexepedted ssl error $err_res') + } + if s.ssl != 0 { + unsafe { C.SSL_free(voidptr(s.ssl)) } + } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } + return error('Could not connect using SSL. ($err_res),err') + } else if res == 0 { + continue + } else if res == 1 { + break + } + } + C.SSL_free(voidptr(s.ssl)) + } + if s.sslctx != 0 { + C.SSL_CTX_free(s.sslctx) + } +} + +// connect to server using open ssl +pub fn (mut s SSLConn) connect(mut tcp_conn net.TcpConn, hostname string) ? { + s.handle = tcp_conn.sock.handle + s.duration = tcp_conn.read_timeout() + + s.sslctx = unsafe { C.SSL_CTX_new(C.SSLv23_client_method()) } + if s.sslctx == 0 { + return error("Couldn't get ssl context") + } + + // TODO: Fix option to enable/disable checks for valid + // certificates to allow both secure and self signed + // for now the checks are not done at all to comply + // to current autobahn tests + + // C.SSL_CTX_set_verify_depth(s.sslctx, 4) + // flags := C.SSL_OP_NO_SSLv2 | C.SSL_OP_NO_SSLv3 | C.SSL_OP_NO_COMPRESSION + // C.SSL_CTX_set_options(s.sslctx, flags) + // mut res := C.SSL_CTX_load_verify_locations(s.sslctx, 'random-org-chain.pem', 0) + + s.ssl = unsafe { &C.SSL(C.SSL_new(s.sslctx)) } + if s.ssl == 0 { + return error("Couldn't create OpenSSL instance.") + } + + // preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4' + // mut res := C.SSL_set_cipher_list(s.ssl, preferred_ciphers.str) + // if res != 1 { + // println('http: openssl: cipher failed') + // } + + mut res := C.SSL_set_tlsext_host_name(voidptr(s.ssl), voidptr(hostname.str)) + if res != 1 { + return error('cannot set host name') + } + + if C.SSL_set_fd(voidptr(s.ssl), tcp_conn.sock.handle) != 1 { + return error("Couldn't assign ssl to socket.") + } + for { + res = C.SSL_connect(voidptr(s.ssl)) + if res != 1 { + err_res := ssl_error(res, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } + return error('Could not connect using SSL. ($err_res),err') + } + break + } +} + +pub fn (mut s SSLConn) socket_read_into_ptr(buf_ptr &byte, len int) ?int { + mut res := 0 + for { + res = C.SSL_read(voidptr(s.ssl), buf_ptr, len) + if res < 0 { + err_res := ssl_error(res, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_zero_return { + return 0 + } + return error('Could not read using SSL. ($err_res)') + } + break + } + return res +} + +pub fn (mut s SSLConn) read_into(mut buffer []byte) ?int { + res := s.socket_read_into_ptr(&byte(buffer.data), buffer.len) ? + return res +} + +// write number of bytes to SSL connection +pub fn (mut s SSLConn) write(bytes []byte) ?int { + unsafe { + mut ptr_base := &byte(bytes.data) + mut total_sent := 0 + for total_sent < bytes.len { + ptr := ptr_base + total_sent + remaining := bytes.len - total_sent + mut sent := C.SSL_write(voidptr(s.ssl), ptr, remaining) + if sent <= 0 { + err_res := ssl_error(sent, s.ssl) ? + if err_res == .ssl_error_want_read { + for { + ready := @select(s.handle, .read, s.duration) ? + if ready { + break + } + } + } else if err_res == .ssl_error_want_write { + for { + ready := @select(s.handle, .write, s.duration) ? + if ready { + break + } + } + continue + } else if err_res == .ssl_error_zero_return { + return error('ssl write on closed connection') // Todo error_with_code close + } + return error_with_code('Could not write SSL. ($err_res),err', int(err_res)) + } + total_sent += sent + } + return total_sent + } +} + +/* +This is basically a copy of Emily socket implementation of select. + This have to be consolidated into common net lib features + when merging this to V +*/ +// [typedef] +// pub struct C.fd_set { +// } + +// Select waits for an io operation (specified by parameter `test`) to be available +fn @select(handle int, test Select, timeout time.Duration) ?bool { + set := C.fd_set{} + + C.FD_ZERO(&set) + C.FD_SET(handle, &set) + + seconds := timeout.milliseconds() / 1000 + microseconds := timeout - (seconds * time.second) + mut tt := C.timeval{ + tv_sec: u64(seconds) + tv_usec: u64(microseconds) + } + + mut timeval_timeout := &tt + + // infinite timeout is signaled by passing null as the timeout to + // select + if timeout == net.infinite_timeout { + timeval_timeout = &C.timeval(0) + } + + match test { + .read { + net.socket_error(C.@select(handle + 1, &set, C.NULL, C.NULL, timeval_timeout)) ? + } + .write { + net.socket_error(C.@select(handle + 1, C.NULL, &set, C.NULL, timeval_timeout)) ? + } + .except { + net.socket_error(C.@select(handle + 1, C.NULL, C.NULL, &set, timeval_timeout)) ? + } + } + + return C.FD_ISSET(handle, &set) +} -- cgit v1.2.3