Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 88 additions & 19 deletions src/ssl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ function TLSServerMethod()
end

const SSL_MODE_AUTO_RETRY = 0x00000004
const SSL_VERIFY_NONE = Cint(0x00)
const SSL_VERIFY_PEER = Cint(0x01)
const SSL_VERIFY_FAIL_IF_NO_PEER_CERT = Cint(0x02)
const SSL_VERIFY_CLIENT_ONCE = Cint(0x04)
const SSL_VERIFY_POST_HANDSHAKE = Cint(0x08)

# Use NetworkOptions for default CA file so that it can be configured using the standard
# environment variables (JULIA_SSL_CA_ROOTS_PATH, SSL_CERT_DIR, and SSL_CERT_FILE).
Expand Down Expand Up @@ -174,27 +179,82 @@ end
function ca_chain!(ssl_context::SSLContext, cacert::String)

if isfile(cacert)
ccall(
(:SSL_CTX_load_verify_locations, libssl),
Cint,
(SSLContext, Ptr{Cchar}, Ptr{Cchar}),
ssl_context,
cacert,
C_NULL)
ssl_load_verify_locations(ssl_context, cacert, nothing)
elseif isdir(cacert)
ccall(
(:SSL_CTX_load_verify_locations, libssl),
Cint,
(SSLContext, Ptr{Cchar}, Ptr{Cchar}),
ssl_context,
C_NULL,
cacert)
ssl_load_verify_locations(ssl_context, nothing, cacert)
else
ArgumentError("Invalid CA certificates location: $cacert")
end

end

"""
ssl_set_verify(ssl_context::SSLContext, mode::Integer)

Sets certificate verification mode for new SSL connections created from this context.
"""
function ssl_set_verify(ssl_context::SSLContext, mode::Integer)
ccall(
(:SSL_CTX_set_verify, libssl),
Cvoid,
(SSLContext, Cint, Ptr{Cvoid}),
ssl_context,
Cint(mode),
C_NULL)
return nothing
end

"""
ssl_load_verify_locations(ssl_context::SSLContext, cafile::Union{Nothing, String}, capath::Union{Nothing, String})

Loads trusted CA certificates from a file and/or directory.
"""
function ssl_load_verify_locations(ssl_context::SSLContext, cafile::Union{Nothing,String}, capath::Union{Nothing,String})
return ccall(
(:SSL_CTX_load_verify_locations, libssl),
Cint,
(SSLContext, Cstring, Cstring),
ssl_context,
isnothing(cafile) ? C_NULL : cafile,
isnothing(capath) ? C_NULL : capath)
end

"""
ssl_load_client_ca_file(cafile::String)

Loads client CA names from a PEM file. Returns an opaque pointer to be passed
to `ssl_set_client_ca_list`. Used for mTLS server configuration to advertise
acceptable client CAs during the TLS handshake.
"""
function ssl_load_client_ca_file(cafile::String)
ca_list = ccall(
(:SSL_load_client_CA_file, libssl),
Ptr{Cvoid},
(Cstring,),
cafile)
if ca_list == C_NULL
throw(OpenSSLError())
end
return ca_list
end

"""
ssl_set_client_ca_list(ssl_context::SSLContext, ca_list::Ptr{Cvoid})

Sets the list of CAs sent to the client when requesting a client certificate.
The `ca_list` should be obtained from `ssl_load_client_ca_file`.
The SSLContext takes ownership of `ca_list`; do not free it manually.
"""
function ssl_set_client_ca_list(ssl_context::SSLContext, ca_list::Ptr{Cvoid})
ccall(
(:SSL_CTX_set_client_CA_list, libssl),
Cvoid,
(SSLContext, Ptr{Cvoid}),
ssl_context,
ca_list)
return nothing
end

function free(ssl_context::SSLContext)
ssl_context.ssl_ctx == C_NULL && return
ccall(
Expand Down Expand Up @@ -388,6 +448,19 @@ function get_error(ssl::SSL, ret::Cint)::SSLErrorCode
ret)
end

"""
ssl_get_verify_result(ssl::SSL)

Returns the result of certificate verification for this SSL connection.
"""
function ssl_get_verify_result(ssl::SSL)
return ccall(
(:SSL_get_verify_result, libssl),
Cint,
(SSL,),
ssl)
end

"""
SSLStream.
"""
Expand Down Expand Up @@ -524,11 +597,7 @@ function Sockets.connect(ssl::SSLStream; require_ssl_verification::Bool=true)
if require_ssl_verification
Base.@lock ssl.lock begin
ssl.closed && throwio(:verify_result)
if (ret = ccall(
(:SSL_get_verify_result, libssl),
Cint,
(SSL,),
ssl.ssl)) != 0
if (ret = ssl_get_verify_result(ssl.ssl)) != 0
throw(OpenSSLError(unsafe_string(ccall(
(:X509_verify_cert_error_string, libcrypto),
Ptr{UInt8},
Expand Down
33 changes: 31 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ end
ssl = SSLStream(ssl_ctx, tcp_stream)

OpenSSL.connect(ssl)
@test OpenSSL.ssl_get_verify_result(ssl.ssl) == 0

x509_server_cert = OpenSSL.get_peer_certificate(ssl)

Expand Down Expand Up @@ -621,8 +622,8 @@ end

@testset "CACertsLoading" begin
certs_dir = joinpath(@__DIR__, "certs")
certs_path = joinpath(certs_dir, "ca-certificates.crt")
certs_path = joinpath(certs_dir, "ca-certificates.crt")

ssl_method = OpenSSL.TLSClientMethod()
ctx = OpenSSL.SSLContext(ssl_method, certs_path)
@test typeof(ctx) == OpenSSL.SSLContext
Expand All @@ -632,3 +633,31 @@ end
@test_throws ErrorException OpenSSL.SSLContext(ssl_method, "does_not_exist")

end

@testset "SSLVerifyBindings" begin
certs_dir = joinpath(@__DIR__, "certs")
certs_path = joinpath(certs_dir, "ca-certificates.crt")

ssl_method = OpenSSL.TLSServerMethod()
ctx = OpenSSL.SSLContext(ssl_method)

mode = OpenSSL.SSL_VERIFY_PEER | OpenSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
OpenSSL.ssl_set_verify(ctx, mode)

current_mode = ccall(
(:SSL_CTX_get_verify_mode, libssl),
Cint,
(OpenSSL.SSLContext,),
ctx)
@test current_mode == mode

@test OpenSSL.ssl_load_verify_locations(ctx, certs_path, nothing) == 1
@test OpenSSL.ssl_load_verify_locations(ctx, nothing, certs_dir) == 1

# Test loading client CA list for mTLS.
ca_list = OpenSSL.ssl_load_client_ca_file(certs_path)
@test ca_list != C_NULL
OpenSSL.ssl_set_client_ca_list(ctx, ca_list)

OpenSSL.free(ctx)
end
Loading