diff --git a/changelog.d/hackney-castore.security b/changelog.d/hackney-castore.security index 44cb90ce6..8d1a0cf4a 100644 --- a/changelog.d/hackney-castore.security +++ b/changelog.d/hackney-castore.security @@ -1 +1 @@ -Hackney and Finch now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs +Hackney, Finch, Mua and the Swoosh HTTP mail adapters now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index fa6fd8020..e6abf4456 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -68,6 +68,21 @@ defmodule Pleroma.Emails.Mailer do end defp parse_config(config) do - Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config) + @otp_app + |> Swoosh.Mailer.parse_config(__MODULE__, @mailer_config, config) + |> put_mua_cacertfile() + end + + @doc false + # Mua otherwise trusts the OS CA store. Keep a CA that the admin set. + def put_mua_cacertfile(config) do + ssl = Keyword.get(config, :ssl, []) + + if config[:adapter] == Swoosh.Adapters.Mua and + not Keyword.has_key?(ssl, :cacertfile) and not Keyword.has_key?(ssl, :cacerts) do + Keyword.put(config, :ssl, [{:cacertfile, CAStore.file_path()} | ssl]) + else + config + end end end diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 32c1080f7..4f0e23f20 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -46,6 +46,24 @@ defmodule Pleroma.HTTP.AdapterHelper do |> adapter_helper().options(uri) end + @doc """ + The configured Tesla adapter with TLS options, for clients that do not go + through `Pleroma.HTTP`. + """ + @spec tesla_adapter() :: module() | {module(), keyword()} + def tesla_adapter do + case adapter() do + Tesla.Adapter.Hackney -> + {Tesla.Adapter.Hackney, Pleroma.HTTP.TLS.put_hackney_ssl_options([])} + + Tesla.Adapter.Gun -> + {Tesla.Adapter.Gun, certificates_verification: true} + + adapter -> + adapter + end + end + defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex index 4d57cfa88..a8c5a3eb9 100644 --- a/lib/pleroma/search/qdrant_search.ex +++ b/lib/pleroma/search/qdrant_search.ex @@ -160,7 +160,7 @@ defmodule Pleroma.Search.QdrantSearch.OpenAIClient do end defp client do - Tesla.client(middleware()) + Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter()) end defp middleware do @@ -191,7 +191,7 @@ defmodule Pleroma.Search.QdrantSearch.QdrantClient do end defp client do - Tesla.client(middleware()) + Tesla.client(middleware(), Pleroma.HTTP.AdapterHelper.tesla_adapter()) end defp middleware do diff --git a/test/pleroma/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs index 2c2b5a4af..d42e8b723 100644 --- a/test/pleroma/emails/mailer_test.exs +++ b/test/pleroma/emails/mailer_test.exs @@ -61,4 +61,33 @@ defmodule Pleroma.Emails.MailerTest do assert hackney_options[:ssl_options] == Pleroma.HTTP.TLS.ssl_options() end + + describe "put_mua_cacertfile/1" do + test "makes Mua trust the CAStore bundle" do + config = Mailer.put_mua_cacertfile(adapter: Swoosh.Adapters.Mua, relay: "mail.example.com") + + assert config[:ssl] == [cacertfile: CAStore.file_path()] + assert config[:relay] == "mail.example.com" + end + + test "keeps other ssl options" do + config = Mailer.put_mua_cacertfile(adapter: Swoosh.Adapters.Mua, ssl: [depth: 3]) + + assert config[:ssl] == [cacertfile: CAStore.file_path(), depth: 3] + end + + test "keeps a CA that the admin set" do + config = [adapter: Swoosh.Adapters.Mua, ssl: [cacertfile: "/etc/ssl/smtp.pem"]] + assert Mailer.put_mua_cacertfile(config) == config + + config = [adapter: Swoosh.Adapters.Mua, ssl: [cacerts: []]] + assert Mailer.put_mua_cacertfile(config) == config + end + + test "leaves other adapters alone" do + config = [adapter: Swoosh.Adapters.SMTP, relay: "mail.example.com"] + + assert Mailer.put_mua_cacertfile(config) == config + end + end end diff --git a/test/pleroma/http/adapter_helper/tesla_adapter_test.exs b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs new file mode 100644 index 000000000..1ee2f13ba --- /dev/null +++ b/test/pleroma/http/adapter_helper/tesla_adapter_test.exs @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.TeslaAdapterTest do + use ExUnit.Case, async: false + + alias Pleroma.HTTP.AdapterHelper + alias Pleroma.HTTP.TLS + + setup do + adapter = Application.get_env(:tesla, :adapter) + on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end) + end + + test "adds the TLS options to Hackney" do + Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney) + + assert AdapterHelper.tesla_adapter() == + {Tesla.Adapter.Hackney, ssl_options: TLS.ssl_options()} + end + + test "turns on certificate checks for Gun" do + Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun) + + assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Gun, certificates_verification: true} + end + + test "keeps other adapters" do + Application.put_env(:tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}) + + assert AdapterHelper.tesla_adapter() == {Tesla.Adapter.Finch, name: MyFinch} + end +end