fix(http): make Hackney trust the CAStore bundle
Hackney trusts its own certifi bundle, which still has the roots of the compromised eMudhra intermediate CAs. Pass explicit ssl_options with the CAStore bundle. Hackney drops all its TLS defaults when ssl_options are given, so keep peer and hostname checks in the options.
This commit is contained in:
parent
6ac6cff5b5
commit
d8c880e494
6 changed files with 102 additions and 3 deletions
1
changelog.d/hackney-castore.security
Normal file
1
changelog.d/hackney-castore.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hackney now trusts only the CAStore certificate bundle instead of its outdated certifi bundle, which still trusted the compromised eMudhra intermediate CAs
|
||||
|
|
@ -28,6 +28,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
|||
|> add_scheme_opts(uri)
|
||||
|> maybe_add_with_body()
|
||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
||||
|> Pleroma.HTTP.TLS.put_hackney_ssl_options()
|
||||
end
|
||||
|
||||
defp add_scheme_opts(opts, _), do: opts
|
||||
|
|
|
|||
35
lib/pleroma/http/tls.ex
Normal file
35
lib/pleroma/http/tls.ex
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.TLS do
|
||||
@moduledoc """
|
||||
TLS client options that trust the CAStore bundle.
|
||||
|
||||
Hackney otherwise trusts its own certifi bundle, and it drops all of its
|
||||
default TLS options, including certificate checks, as soon as any
|
||||
`ssl_options` are given.
|
||||
"""
|
||||
|
||||
@spec ssl_options(keyword()) :: keyword()
|
||||
def ssl_options(overrides \\ []) do
|
||||
Keyword.merge(
|
||||
[
|
||||
verify: :verify_peer,
|
||||
cacertfile: CAStore.file_path(),
|
||||
depth: 20,
|
||||
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
|
||||
],
|
||||
overrides
|
||||
)
|
||||
end
|
||||
|
||||
@spec put_hackney_ssl_options(keyword()) :: keyword()
|
||||
def put_hackney_ssl_options(opts) do
|
||||
if opts[:insecure] do
|
||||
opts
|
||||
else
|
||||
Keyword.put(opts, :ssl_options, ssl_options(Keyword.get(opts, :ssl_options, [])))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -28,9 +28,9 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
|
|||
@impl true
|
||||
def request(method, url, headers, body, opts \\ []) do
|
||||
opts =
|
||||
Keyword.put_new(opts, :path_encode_fun, fn path ->
|
||||
path
|
||||
end)
|
||||
opts
|
||||
|> Keyword.put_new(:path_encode_fun, fn path -> path end)
|
||||
|> Pleroma.HTTP.TLS.put_hackney_ssl_options()
|
||||
|
||||
if opts[:follow_redirect] != false do
|
||||
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
|
||||
|
|
|
|||
|
|
@ -39,5 +39,20 @@ defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do
|
|||
assert opts[:b] == 1
|
||||
refute Keyword.has_key?(opts, :proxy)
|
||||
end
|
||||
|
||||
test "trusts the CAStore bundle", %{uri: uri} do
|
||||
opts = Hackney.options([], uri)
|
||||
|
||||
assert opts[:ssl_options] == Pleroma.HTTP.TLS.ssl_options()
|
||||
end
|
||||
|
||||
test "keeps the CAStore bundle with configured ssl_options", %{uri: uri} do
|
||||
clear_config([:http, :adapter], ssl_options: [versions: [:"tlsv1.2"]])
|
||||
|
||||
opts = Hackney.options([], uri)
|
||||
|
||||
assert opts[:ssl_options][:versions] == [:"tlsv1.2"]
|
||||
assert opts[:ssl_options][:cacertfile] == CAStore.file_path()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
47
test/pleroma/http/tls_test.exs
Normal file
47
test/pleroma/http/tls_test.exs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.TLSTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pleroma.HTTP.TLS
|
||||
|
||||
describe "ssl_options/1" do
|
||||
test "verifies peers against the CAStore bundle" do
|
||||
opts = TLS.ssl_options()
|
||||
|
||||
assert opts[:verify] == :verify_peer
|
||||
assert opts[:cacertfile] == CAStore.file_path()
|
||||
assert [match_fun: match_fun] = opts[:customize_hostname_check]
|
||||
assert is_function(match_fun, 2)
|
||||
end
|
||||
|
||||
test "keeps the CA bundle when other options are given" do
|
||||
opts = TLS.ssl_options(versions: [:"tlsv1.2"])
|
||||
|
||||
assert opts[:versions] == [:"tlsv1.2"]
|
||||
assert opts[:cacertfile] == CAStore.file_path()
|
||||
assert opts[:verify] == :verify_peer
|
||||
end
|
||||
end
|
||||
|
||||
describe "put_hackney_ssl_options/1" do
|
||||
test "adds the TLS options" do
|
||||
opts = TLS.put_hackney_ssl_options(pool: :media)
|
||||
|
||||
assert opts[:pool] == :media
|
||||
assert opts[:ssl_options] == TLS.ssl_options()
|
||||
end
|
||||
|
||||
test "merges given ssl_options into the TLS options" do
|
||||
opts = TLS.put_hackney_ssl_options(ssl_options: [versions: [:"tlsv1.2"]])
|
||||
|
||||
assert opts[:ssl_options] == TLS.ssl_options(versions: [:"tlsv1.2"])
|
||||
end
|
||||
|
||||
test "leaves insecure requests alone" do
|
||||
assert TLS.put_hackney_ssl_options(insecure: true) == [insecure: true]
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue