Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibGit2: improve error when CA root cert can't be set #38827

Merged
merged 1 commit into from
Dec 15, 2020
Merged
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
52 changes: 37 additions & 15 deletions stdlib/LibGit2/src/LibGit2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -961,13 +961,19 @@ end

## lazy libgit2 initialization

const ENSURE_INITIALIZED_LOCK = ReentrantLock()

function ensure_initialized()
x = Threads.atomic_cas!(REFCOUNT, 0, 1)
if x < 0
negative_refcount_error(x)::Union{}
end
if x == 0
initialize()
lock(ENSURE_INITIALIZED_LOCK) do
x = Threads.atomic_cas!(REFCOUNT, 0, 1)
x > 0 && return
x < 0 && negative_refcount_error(x)::Union{}
try initialize()
catch
Threads.atomic_sub!(REFCOUNT, 1)
@assert REFCOUNT[] == 0
rethrow()
end
end
return nothing
end
Expand All @@ -979,24 +985,40 @@ end
@noinline function initialize()
@check ccall((:git_libgit2_init, :libgit2), Cint, ())

cert_loc = NetworkOptions.ca_roots()
cert_loc !== nothing && set_ssl_cert_locations(cert_loc)

atexit() do
# refcount zero, no objects to be finalized
if Threads.atomic_sub!(REFCOUNT, 1) == 1
ccall((:git_libgit2_shutdown, :libgit2), Cint, ())
end
end

cert_loc = NetworkOptions.ca_roots()
cert_loc !== nothing && set_ssl_cert_locations(cert_loc)
end

function set_ssl_cert_locations(cert_loc)
cert_file = isfile(cert_loc) ? cert_loc : Cstring(C_NULL)
cert_dir = isdir(cert_loc) ? cert_loc : Cstring(C_NULL)
cert_file == C_NULL && cert_dir == C_NULL && return
@check ccall((:git_libgit2_opts, :libgit2), Cint,
(Cint, Cstring...),
Cint(Consts.SET_SSL_CERT_LOCATIONS), cert_file, cert_dir)
cert_file = cert_dir = Cstring(C_NULL)
if isdir(cert_loc) # directories
cert_dir = cert_loc
else # files, /dev/null, non-existent paths, etc.
cert_file = cert_loc
end
ret = ccall((:git_libgit2_opts, :libgit2), Cint, (Cint, Cstring...),
Cint(Consts.SET_SSL_CERT_LOCATIONS), cert_file, cert_dir)
ret >= 0 && return ret
err = Error.GitError(ret)
err.class == Error.SSL &&
err.msg == "TLS backend doesn't support certificate locations" ||
throw(err)
var = nothing
for v in NetworkOptions.CA_ROOTS_VARS
haskey(ENV, v) && (var = v)
end
@assert var !== nothing # otherwise we shouldn't be here
msg = """
Your Julia is built with a SSL/TLS engine that libgit2 doesn't know how to configure to use a file or directory of certificate authority roots, but your environment specifies one via the $var variable. If you believe your system's root certificates are safe to use, you can `export JULIA_SSL_CA_ROOTS_PATH=""` in your environment to use those instead.
"""
throw(Error.GitError(err.class, err.code, chomp(msg)))
end

end # module
53 changes: 53 additions & 0 deletions stdlib/LibGit2/test/bad_ca_roots.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module Test_LibGit2_https

using Test, LibGit2, NetworkOptions

# we currently use system SSL/TLS on macOS and Windows platforms
# and libgit2 cannot set the CA roots path on those systems
# if that changes, this may need to be adjusted
const CAN_SET_CA_ROOTS_PATH = !Sys.isapple() && !Sys.iswindows()

@testset "empty CA roots file" begin
# these fail for different reasons on different platforms:
# - on Apple & Windows you cannot set the CA roots path location
# - on Linux & FreeBSD you you can but these are invalid files
ENV["JULIA_SSL_CA_ROOTS_PATH"] = "/dev/null"
@test_throws LibGit2.GitError LibGit2.ensure_initialized()
ENV["JULIA_SSL_CA_ROOTS_PATH"] = tempname()
@test_throws LibGit2.GitError LibGit2.ensure_initialized()
# test that it still fails if called a second time
@test_throws LibGit2.GitError LibGit2.ensure_initialized()
if !CAN_SET_CA_ROOTS_PATH
# test that this doesn't work on macOS & Windows
ENV["JULIA_SSL_CA_ROOTS_PATH"] = NetworkOptions.bundled_ca_roots()
@test_throws LibGit2.GitError LibGit2.ensure_initialized()
delete!(ENV, "JULIA_SSL_CA_ROOTS_PATH")
@test LibGit2.ensure_initialized() === nothing
end
end

if CAN_SET_CA_ROOTS_PATH
@testset "non-empty but bad CA roots file" begin
# should still be possible to initialize
ENV["JULIA_SSL_CA_ROOTS_PATH"] = joinpath(@__DIR__, "bad_ca_roots.pem")
@test LibGit2.ensure_initialized() === nothing
end
mktempdir() do dir
repo_url = "https://github.com/JuliaLang/Example.jl"
@testset "HTTPS clone with bad CA roots fails" begin
repo_path = joinpath(dir, "Example.HTTPS")
c = LibGit2.CredentialPayload(allow_prompt=false, allow_git_helpers=false)
redirect_stderr(devnull)
err = try LibGit2.clone(repo_url, repo_path, credentials=c)
catch err
err
end
@test err isa LibGit2.GitError
@test err.msg == "user rejected certificate for github.com"
end
end
end

end # module
22 changes: 22 additions & 0 deletions stdlib/LibGit2/test/bad_ca_roots.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDtDCCApwCCQDeWk9ywtjrpTANBgkqhkiG9w0BAQsFADCBmzELMAkGA1UEBhMC
VVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhOZXcgWW9yazEnMCUGA1UE
CgweVGhlIEp1bGlhIFByb2dyYW1taW5nIExhbmd1YWdlMRYwFAYDVQQDDA1qdWxp
YWxhbmcub3JnMSUwIwYJKoZIhvcNAQkBFhZzZWN1cml0eUBqdWxpYWxhbmcub3Jn
MB4XDTIwMTIxMTE3NTgxN1oXDTI1MTIxMDE3NTgxN1owgZsxCzAJBgNVBAYTAlVT
MREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwITmV3IFlvcmsxJzAlBgNVBAoM
HlRoZSBKdWxpYSBQcm9ncmFtbWluZyBMYW5ndWFnZTEWMBQGA1UEAwwNanVsaWFs
YW5nLm9yZzElMCMGCSqGSIb3DQEJARYWc2VjdXJpdHlAanVsaWFsYW5nLm9yZzCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCFgRMFlNGIgmZtMzR+Xx+t
cPXpYnw9sZXlGy4y+P+UVW5rnFtf+OL4WkcJykmL3n/iLBKpdrndhzL7zuc6lGVv
G6u+Gvwg5uCZ4RqiFSPP9xK7tl7H+CwrtWL/2vF1wlYC228A+NMpPyQw4XtX1L8G
xAvJbFz8JrJ+WH1wCmVpkxA6pnpK+DZ/QKPVwa/qhB80ur3bYwlHXWwDBf8bq98f
7wDBpJoEc3IG3GYopP6ie5KTENKxbDZjr306ZuxTLjXKrAE/OJkAiGKJ7gPlwT/E
kFI/x/No9Y/fPWFRGiFo2L4fhP2Mohcph3PQswFKfnQlMQzztetDKWCZveB5HisC
AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAqAaFA93Q3VWWKAZBqORT+6N2iHDiOxMu
Ol8Jjqp3Spj552NbyPPpfF2a2Q/Bh2ZAmncCoGTpuXdnowSHyXuxPey6BIvEbq0L
FizTNuIzaA95fO/ce9LNujxliDHhKMJBZtCqBJYJ4dgd9sA4/LeAG/P3ltIY6K8P
22AAx2bzWbeRJSqxeBodm19rOb9Yz2SOaZIam42E+xia+hsUFdGf6Zkfpa02azDm
93EjS+DwapqxAKgkps6JuKqpRFdZd8QsVmgAcapnIt77w8sfBu9eyITF/Tm+MA8k
IRieSypM7TK0jQ6QrNV7FKSI6eEPaqWBMwkLg3S5H6KQMntVRlcc0A==
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions stdlib/LibGit2/test/online.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,12 @@ mktempdir() do dir
end
end

# needs to be run in separate process so it can re-initialize libgit2
# with a useless self-signed certificate authority root certificate
file = joinpath(@__DIR__, "bad_ca_roots.jl")
cmd = `$(Base.julia_cmd()) --depwarn=no --startup-file=no $file`
if !success(pipeline(cmd; stdout=stdout, stderr=stderr))
error("bad CA roots tests failed, cmd : $cmd")
end

end # module