Skip to content

Instantly share code, notes, and snippets.

@rasa-silva
Created April 18, 2023 17:36
Show Gist options
  • Save rasa-silva/b5580eb7329c2178fa0de1de090090f4 to your computer and use it in GitHub Desktop.
Save rasa-silva/b5580eb7329c2178fa0de1de090090f4 to your computer and use it in GitHub Desktop.
Basic TLS client in Odin and libtls
package main
import "core:bytes"
import "core:c"
import "core:fmt"
import "core:time"
foreign import libtls "system:tls"
TLS_PROTOCOL_TLSv1_0 :: (1 << 1)
TLS_PROTOCOL_TLSv1_1 :: (1 << 2)
TLS_PROTOCOL_TLSv1_2 :: (1 << 3)
TLS_PROTOCOL_TLSv1_3 :: (1 << 4)
TLS_PROTOCOLS_DEFAULT :: TLS_PROTOCOL_TLSv1_2|TLS_PROTOCOL_TLSv1_3
TLS_WANT_POLLIN :: -2
TLS_WANT_POLLOUT :: -3
TlsConfig :: struct {}
TlsContext :: struct {}
foreign libtls {
tls_config_new :: proc() -> ^TlsConfig ---
tls_config_insecure_noverifycert :: proc(cfg: ^TlsConfig) ---
tls_config_insecure_noverifyname :: proc(cfg: ^TlsConfig) ---
tls_config_insecure_noverifytime :: proc(cfg: ^TlsConfig) ---
tls_config_parse_protocols :: proc(protocols : ^c.uint32_t, protos: cstring) -> c.int ---
tls_config_set_protocols :: proc(cfg: ^TlsConfig, protocols: c.uint32_t) -> c.int ---
tls_config_set_ciphers :: proc(cfg: ^TlsConfig, ciphers: cstring) ---
tls_config_set_dheparams :: proc(cfg: ^TlsConfig, params: cstring) -> c.int ---
tls_config_set_ecdhecurves :: proc(cfg: ^TlsConfig, curves: cstring) -> c.int ---
tls_config_set_ca_file :: proc(cfg: ^TlsConfig, ca_file: cstring) -> c.int ---
tls_client :: proc() -> ^TlsContext ---
tls_free :: proc(ctx: ^TlsContext) ---
tls_configure :: proc(ctx: ^TlsContext, cfg: ^TlsConfig) -> c.int ---
tls_connect :: proc(ctx: ^TlsContext, host: cstring, port: cstring) -> c.int ---
tls_connect_servername :: proc(ctx: ^TlsContext, host: cstring, port: cstring, servername: cstring) -> c.int ---
tls_handshake :: proc(ctx: ^TlsContext) -> c.int ---
tls_error :: proc(ctx: ^TlsContext) -> cstring ---
tls_close :: proc(ctx: ^TlsContext) -> c.int ---
tls_reset :: proc(ctx: ^TlsContext) ---
tls_read :: proc(ctx: ^TlsContext, buf: []u8, len: c.int) -> c.int ---
tls_write :: proc(ctx: ^TlsContext, buf: []u8, len: c.int) -> c.int ---
}
main :: proc() {
start := time.now()
cfg := tls_config_new()
// tls_config_insecure_noverifycert(cfg)
// tls_config_insecure_noverifyname(cfg)
// tls_config_insecure_noverifytime(cfg)
// tls_config_set_ca_file(cfg, "/etc/ssl/cert.pem")
// tls_config_set_ciphers(cfg, "compat")
// tls_config_set_dheparams(cfg, "auto")
// tls_config_set_ecdhecurves(cfg, "default")
res := tls_config_set_protocols(cfg, TLS_PROTOCOLS_DEFAULT)
if res < 0 {
fmt.println("error in set protocols:", res)
}
ctx := tls_client()
fmt.println("ctx", ctx)
res = tls_configure(ctx, cfg)
fmt.println("config res", res)
res = tls_connect(ctx, "odin-lang.org", "443")
if res == -1 {
fmt.println("connect error:", tls_error(ctx))
return
}
fmt.println("connected")
//not needed explicitly but it's useful for checking the negotiated values
res = tls_handshake(ctx)
if res < 0 {
fmt.println("handshake error:", tls_error(ctx))
return
}
fmt.println("writing...")
network_start := time.now()
request := "GET / HTTP/1.1\r\nHost: odin-lang.org\r\nConnection: close\r\n\r\n"
w_bytes := tls_write(ctx, transmute([]u8)request, i32(len(request)))
if w_bytes < 0 {
fmt.println("write error:", tls_error(ctx))
return
}
fmt.println("wrote", w_bytes, "bytes")
fmt.println("reading...")
response: bytes.Buffer
read_buf: [1024]u8
for {
r_bytes := tls_read(ctx, read_buf[:], len(read_buf))
if r_bytes == -1 {
fmt.println("read error:", r_bytes, ":", tls_error(ctx))
break
}
if r_bytes == TLS_WANT_POLLIN do continue
if r_bytes == 0 do break
bytes.buffer_write(&response, read_buf[:r_bytes])
}
fmt.println("Done.")
tls_close(ctx)
tls_free(ctx)
fmt.println("init time:", time.duration_milliseconds(time.diff(start, network_start)), "ms")
fmt.println("req/resp time:", time.duration_milliseconds(time.since(network_start)), "ms")
fmt.println("total time:", time.duration_milliseconds(time.since(start)), "ms")
fmt.println("response len:", bytes.buffer_length(&response), "bytes")
fmt.println(bytes.buffer_to_string(&response))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment