commit fff6f7445650932889cc39c000a78de316b9a2e1 Author: nixo Date: Fri Oct 23 19:34:36 2020 +0200 init diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..a154b04 --- /dev/null +++ b/Project.toml @@ -0,0 +1,4 @@ +name = "OpenSSL" +uuid = "13378672-9967-48ef-8ae3-b2866786e7a0" +authors = ["nixo "] +version = "0.1.0" diff --git a/src/OpenSSL.jl b/src/OpenSSL.jl new file mode 100644 index 0000000..fee43d6 --- /dev/null +++ b/src/OpenSSL.jl @@ -0,0 +1,43 @@ +module OpenSSL + +export ca_chain!, close + +const libssl = "/gnu/store/hcxpkksmbql6s4al8yy2myr25kh4cic0-openssl-1.1.1g/lib/libssl.so" + +include("consts.jl") +include("evp_md.jl") +include("types.jl") +export SSLContext, SSLClient +include("bio.jl") + +include("context.jl") + +include("ssl.jl") + +include("certs.jl") + +include("flow_control.jl") + +import Base.read +function read(client::SSLClient) + buf = Vector{UInt8}(undef, 64) + n = ssl_read!(client, buf) + return (n, buf) +end + +import Base.write +function write(client::SSLClient, data) + send_unencrypted_bytes(client, data) + do_encrypt(client) + do_sock_write(client) +end +write(c::SSLClient, s::String) = write(c, Vector{UInt8}(s)) + +import Base.close +function close(client::SSLClient) + shutdown(client) + close(client.sock) + free(client) +end + +end # module diff --git a/src/bio.jl b/src/bio.jl new file mode 100644 index 0000000..ec5a29f --- /dev/null +++ b/src/bio.jl @@ -0,0 +1,26 @@ +s_mem() = ccall((:BIO_s_mem, libssl), Ptr{Cvoid}, ()) +bio_new(mem::Ptr{Cvoid}) = ccall((:BIO_new, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), mem) +bio_new() = bio_new(s_mem()) + +function set_bio!(client::SSLClient, rbio, wbio) + client.rbio = rbio + client.wbio = wbio + ccall((:SSL_set_bio, libssl), Ptr{Cvoid}, (Ptr{Cvoid},Ptr{Cvoid},Ptr{Cvoid}), + client.ssl, rbio, wbio) +end + +function bio_write!(client::SSLClient, data) + ccall((:BIO_write, libssl), Cint, (Ptr{Cvoid},Ptr{Cvoid},Cint), + client.rbio, data, length(data)) +end + +function bio_read(client::SSLClient) + buff = Vector{UInt8}(undef, 64) + n = ccall((:BIO_read, libssl), Cint, (Ptr{Cvoid},Ptr{Cvoid},Cint), + client.wbio, buff, length(buff)) + n, n > 0 ? buff[1:n] : buff +end + +bio_test_flags(bio, flags) = + ccall((:BIO_test_flags, libssl), Cint, (Ptr{Cvoid},Cint), bio, flags) != 0 +bio_should_retry(bio) = bio_test_flags(bio, BIO_FLAGS_SHOULD_RETRY) diff --git a/src/certs.jl b/src/certs.jl new file mode 100644 index 0000000..7407499 --- /dev/null +++ b/src/certs.jl @@ -0,0 +1,46 @@ +function ca_chain!(context::SSLContext, crt_file, key_file) + res = ccall((:SSL_CTX_use_certificate_file, libssl), Cint, (Ptr{Cvoid}, Cstring, Cint), + context.ptr, crt_file, SSL_FILETYPE_PEM) + if res <= 0 + @error res + end + res = ccall((:SSL_CTX_use_PrivateKey_file, libssl), Cint, (Ptr{Cvoid}, Cstring, Cint), + context.ptr, key_file, SSL_FILETYPE_PEM) + if res <= 0 + @error res + end + if ccall((:SSL_CTX_check_private_key, libssl), Cint, (Ptr{Cvoid}, ), context.ptr) != 1 + @error "Private and certificate is not matching" + end + return nothing +end + +get_peer_certificate(client::SSLClient) = + ccall((:SSL_get_peer_certificate, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), client.ssl) + +verify_certificate(client::SSLClient) = + ccall((:SSL_get_verify_result, libssl), Clong, (Ptr{Cvoid},), + client.ssl) + +function X509_get_serialNumber(X509) + asn_ptr = ccall((:X509_get_serialNumber, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), X509) + res = Ref{Clong}(0) + # TODO: check return value (1:ok) + # TODO: Move to own func and call it + n = ccall((:ASN1_INTEGER_get_int64, libssl), Cint, (Ref{Clong},Ptr{Cvoid}), + res, asn_ptr) + res[] +end + +function X509_digest(X509; md_type = EVP_sha1()) + len = Ref{Cint}(0) + md = zeros(UInt8, EVP_MAX_MD_SIZE) + res = ccall((:X509_digest, libssl), Cint, + (Ptr{Cvoid}, Ptr{Cvoid}, Ref{Cuchar}, Ref{Cint}), + X509, md_type, md, len) == 1 + !res && throw("Error") + md[1:len[]] +end + +digest_to_string(d::Array{UInt8}) = join(string.(d, base = 16), ":") +digest_to_string_62(d::Vector{T}) where T = join(string.(reinterpret(UInt32, d), base = 62), ":") diff --git a/src/consts.jl b/src/consts.jl new file mode 100644 index 0000000..fba0f8d --- /dev/null +++ b/src/consts.jl @@ -0,0 +1,39 @@ +const DEFAULT_BUF_SIZE = 64 + +# TODO: Add here the missing related to those three +const SSL_FILETYPE_PEM = 1 +const SSL_OP_NO_SSLv3 = 0x02000000 +const EVP_MAX_MD_SIZE = 64 + +const BIO_FLAGS_READ = 0x01 +const BIO_FLAGS_WRITE = 0x02 +const BIO_FLAGS_IO_SPECIAL = 0x03 +const BIO_FLAGS_RWS = (BIO_FLAGS_READ|BIO_FLAGS_WRITE|BIO_FLAGS_IO_SPECIAL) +const BIO_FLAGS_SHOULD_RETRY = 0x08 +const BIO_FLAGS_MEM_RDONLY = 0x200 +const BIO_FLAGS_NONCLEAR_RST = 0x400 +const BIO_FLAGS_IN_EOF = 0x800 + +@enum SSL_ERRORS begin + SSL_ERROR = 1 + SSL_WANT_READ = 2 + SSL_WANT_WRITE = 3 + SSL_SYSCALL = 5 + SSL_ZERO_RETURN = 6 +end + +@enum SSL_VERIFY begin + VERIFY_NONE = 0x00 + VERIFY_PEER = 0x01 + VERIFY_FAIL_IF_NO_PEER_CERT = 0x02 + VERIFY_CLIENT_ONCE = 0x04 + VERIFY_POST_HANDSHAKE = 0x08 +end + +@enum SSL_VERSION begin + SSL3_VERSION = 0x0300 + TLS1_VERSION + TLS1_1_VERSION + TLS1_2_VERSION + TLS1_3_VERSION +end diff --git a/src/context.jl b/src/context.jl new file mode 100644 index 0000000..8c19006 --- /dev/null +++ b/src/context.jl @@ -0,0 +1,19 @@ +function set_options!(ctx::SSLContext, options::UInt32) + ccall((:SSL_CTX_set_options, libssl), Ptr{Cvoid}, (Ptr{Cvoid}, Clong), + ctx.ptr, options) + nothing +end + +function set_verify_mode(ctx::SSLContext, mode, callback = C_NULL) + ccall((:SSL_CTX_set_verify, libssl), Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cvoid}), + ctx.ptr, mode, callback) +end + +post_handshake_auth(ctx::SSLContext; enable::Bool = true) = + ccall((:SSL_CTX_set_post_handshake_auth, libssl), Cvoid, (Ptr{Cvoid}, Cint), ctx.ptr, enable ? 1 : 0) + +function set_verify_callback(ctx::SSLContext, callback) + args = C_NULL + ccall((:SSL_CTX_set_cert_verify_callback, libssl), Cvoid, + (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), ctx.ptr, callback, args) +end diff --git a/src/evp_md.jl b/src/evp_md.jl new file mode 100644 index 0000000..a805496 --- /dev/null +++ b/src/evp_md.jl @@ -0,0 +1,23 @@ +EVP_md_null() = ccall((:EVP_md_null, libssl), Ptr{Cvoid}, ()) +EVP_md4() = ccall((:EVP_md4, libssl), Ptr{Cvoid}, ()) +EVP_md5() = ccall((:EVP_md5, libssl), Ptr{Cvoid}, ()) +EVP_md5_sha1() = ccall((:EVP_md5_sha1, libssl), Ptr{Cvoid}, ()) +EVP_blake2b512() = ccall((:EVP_blake2b512, libssl), Ptr{Cvoid}, ()) +EVP_blake2s256() = ccall((:EVP_blake2s256, libssl), Ptr{Cvoid}, ()) +EVP_sha1() = ccall((:EVP_sha1, libssl), Ptr{Cvoid}, ()) +EVP_sha224() = ccall((:EVP_sha224, libssl), Ptr{Cvoid}, ()) +EVP_sha256() = ccall((:EVP_sha256, libssl), Ptr{Cvoid}, ()) +EVP_sha384() = ccall((:EVP_sha384, libssl), Ptr{Cvoid}, ()) +EVP_sha512() = ccall((:EVP_sha512, libssl), Ptr{Cvoid}, ()) +EVP_sha512_224() = ccall((:EVP_sha512_224, libssl), Ptr{Cvoid}, ()) +EVP_sha512_256() = ccall((:EVP_sha512_256, libssl), Ptr{Cvoid}, ()) +EVP_sha3_224() = ccall((:EVP_sha3_224, libssl), Ptr{Cvoid}, ()) +EVP_sha3_256() = ccall((:EVP_sha3_256, libssl), Ptr{Cvoid}, ()) +EVP_sha3_384() = ccall((:EVP_sha3_384, libssl), Ptr{Cvoid}, ()) +EVP_sha3_512() = ccall((:EVP_sha3_512, libssl), Ptr{Cvoid}, ()) +EVP_shake128() = ccall((:EVP_shake128, libssl), Ptr{Cvoid}, ()) +EVP_shake256() = ccall((:EVP_shake256, libssl), Ptr{Cvoid}, ()) +EVP_mdc2() = ccall((:EVP_mdc2, libssl), Ptr{Cvoid}, ()) +EVP_ripemd160() = ccall((:EVP_ripemd160, libssl), Ptr{Cvoid}, ()) +EVP_whirlpool() = ccall((:EVP_whirlpool, libssl), Ptr{Cvoid}, ()) +EVP_sm3() = ccall((:EVP_sm3, libssl), Ptr{Cvoid}, ()) diff --git a/src/flow_control.jl b/src/flow_control.jl new file mode 100644 index 0000000..0a8db3e --- /dev/null +++ b/src/flow_control.jl @@ -0,0 +1,129 @@ + +function queue_encrypted_bytes(client, buf) + # println("Adding $(length(buf)) for a total of $(length(buf) + length(client.write_buf))") + append!(client.write_buf, buf) +end + +function do_ssl_handshake(client) + n = SSL_handshake(client) + status = ssl_status(client, n) + if status in (SSL_WANT_READ, SSL_WANT_WRITE) + while true + (n, buf) = bio_read(client) + # println("bio_read: $(n)") + if n > 0 + queue_encrypted_bytes(client, buf) + elseif !bio_should_retry(client.wbio) + return -1 + end + if n <= 0 + break + end + end + end + # println("End of ssl handshake") + return status +end + +function send_unencrypted_bytes(client, buf) + append!(client.encrypt_buf, buf) +end + +function do_encrypt(client) + ssl_init_finished(client) || return 0 + + while length(client.encrypt_buf) > 0 + n = ssl_write(client, client.encrypt_buf) + status = ccall((:SSL_get_error, libssl), SSL_ERRORS, (Ptr{Cvoid}, Cint), + client.ssl, n) + if n > 0 + client.encrypt_buf = client.encrypt_buf[(n+1):end] + while true + (n, buf) = bio_read(client) + if n > 0 + queue_encrypted_bytes(client, buf) + elseif !bio_should_retry(client.wbio) + return -1 + end + n > 0 || break + end + end + if status in (SSL_ERROR, SSL_SYSCALL) + @warn "SSL ERROR" + return -1 + end + if n == 0 + break + end + end + return 0 +end + + +function on_read_cb(client, buffer) + idx = 1 + len = length(buffer) + while len > 0 + n = bio_write!(client, buffer[idx:end]) + if n <= 0 + return -1 + end + idx += n + len -= n + + if ! ssl_init_finished(client) + # println("SSL not finished yet") + if (do_ssl_handshake(client) == -1) + return -1 + end + if !ssl_init_finished(client) + return 0 + end + end + # println("NOW IT FINISHED\n") + while true + (n, buf) = read(client) + if n > 0 + client.io_on_read(buf[1:n]) + else + break + end + end + status = ssl_status(client, n) + if status in (SSL_WANT_READ, SSL_WANT_WRITE) + while true + (n, buf) = bio_read(client) + if n > 0 + queue_encrypted_bytes(client, buf) + elseif !bio_should_retry(client.wbio) + return -1 + end + n < 0 && break + end + end + if status in (SSL_ERROR, SSL_SYSCALL) + return -1 + end + end + return 0 +end + +function do_sock_read(client::SSLClient) + buf = readavailable(client.sock) + if length(buf) > 0 + return on_read_cb(client, buf) + else + return -1 + end +end + +function do_sock_write(client::SSLClient) + n = write(client.sock, client.write_buf) + # println("Written to the client: $n") + if n > 0 + client.write_buf = client.write_buf[(n+1):end] + return 0 + else + return -1 + end +end diff --git a/src/ssl.jl b/src/ssl.jl new file mode 100644 index 0000000..23801ab --- /dev/null +++ b/src/ssl.jl @@ -0,0 +1,34 @@ +ssl_version(client) = + ccall((:SSL_client_version, libssl), SSL_VERSION, (Ptr{Cvoid}, ), client.ssl) + +SSL_handshake(client::SSLClient) = + ccall((:SSL_do_handshake, libssl), Cint, (Ptr{Cvoid},), client.ssl) + +SSL_new(ctx::SSLContext) = + ccall((:SSL_new, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), ctx.ptr) + +SSL_accept_state(client::SSLClient) = + ccall((:SSL_set_accept_state, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), client.ssl) + +ssl_init_finished(client::SSLClient) = + ccall((:SSL_is_init_finished, libssl), Bool, (Ptr{Cvoid},), client.ssl) + +accept(client::SSLClient) = + ccall((:SSL_accept, libssl), Cint, (Ptr{Cvoid}, ), client.ssl) + +ssl_status(client::SSLClient, n) = + ccall((:SSL_get_error, libssl), SSL_ERRORS, (Ptr{Cvoid}, Cint), client.ssl, n) + +ssl_read!(client::SSLClient, buf::Vector{UInt8}) = + ccall((:SSL_read, libssl), Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Cint), + client.ssl, buf, length(buf)) + +ssl_write(client::SSLClient, buf::Vector{UInt8}) = + ccall((:SSL_write, libssl), Cint, (Ptr{Cvoid}, Ptr{Cvoid}, Cint), + client.ssl, buf, length(buf)) + +shutdown(client::SSLClient) = + ccall((:SSL_shutdown, libssl), Cint, (Ptr{Cvoid},), client.ssl) + +free(client::SSLClient) = + ccall((:SSL_free, libssl), Cint, (Ptr{Cvoid},), client.ssl) diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..07f5e2e --- /dev/null +++ b/src/types.jl @@ -0,0 +1,37 @@ +mutable struct SSLContext <: IO + data + ptr::Ptr{Cvoid} + + function SSLContext() + ssl_context = new() + method = ccall((:TLS_server_method, libssl), Ptr{Cvoid}, ()) + ssl_context.ptr = ccall((:SSL_CTX_new, libssl), Ptr{Cvoid}, (Ptr{Cvoid},), method) + # if ssl_context.ptr == 0 + # end + ssl_context + end +end + +mutable struct SSLClient{T} + rbio::Ptr{Cvoid} + wbio::Ptr{Cvoid} + context::SSLContext + ssl::Ptr{Cvoid} + io_on_read + sock::T + write_buf::Vector{UInt8} + encrypt_buf::Vector{UInt8} + + function SSLClient(ctx::SSLContext, io::T) where T + client = new{T}() + client.context = ctx + client.ssl = SSL_new(ctx) + client.io_on_read = (data) -> nothing + SSL_accept_state(client) + set_bio!(client, bio_new(), bio_new()) + client.write_buf = UInt8[] + client.encrypt_buf = UInt8[] + client.sock = io + client + end +end