From 6ac6cff5b5de4ed1a917902ad20b043fb4cb40cc Mon Sep 17 00:00:00 2001 From: Phantasm Date: Tue, 20 Oct 2026 23:42:28 +0200 Subject: [PATCH 1/6] Sec: Bump castore to version 1.0.22 eMudhra Intermediate CA Compromise --- changelog.d/castore-1.0.22.security | 1 + mix.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/castore-1.0.22.security diff --git a/changelog.d/castore-1.0.22.security b/changelog.d/castore-1.0.22.security new file mode 100644 index 000000000..8d702f382 --- /dev/null +++ b/changelog.d/castore-1.0.22.security @@ -0,0 +1 @@ +Updated CAStore to version 1.0.22 with updated Mozilla certificate bundle after eMudhra Intermediate CA Compromise diff --git a/mix.lock b/mix.lock index bca6f777b..58b5a1f58 100644 --- a/mix.lock +++ b/mix.lock @@ -11,7 +11,7 @@ "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:hex, :pleroma_captcha, "1.0.3", "6cc1440e91a092653fe909f028cc4c5d83ea858b1439e3b9a85e446382e2b9a3", [:make, :mix], [], "hexpm", "477f62c1a845a9458c546658223295f958f98136acef89c05beb278b1b6f4a14"}, - "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, + "castore": {:hex, :castore, "1.0.22", "a01978e6a5d42fa3a34143630588f92036e7d90e2309ecdd466722d1279a2213", [:mix], [], "hexpm", "c2a4d42723b4affbc1c3e15d8f6df3f7d8f1f627c84de643383634cce58493d5"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, From d8c880e4946afa2710ae7e897082db982245b095 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 21 Oct 2026 08:21:43 +0400 Subject: [PATCH 2/6] 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. --- changelog.d/hackney-castore.security | 1 + lib/pleroma/http/adapter_helper/hackney.ex | 1 + lib/pleroma/http/tls.ex | 35 ++++++++++++++ lib/pleroma/reverse_proxy/client/hackney.ex | 6 +-- .../http/adapter_helper/hackney_test.exs | 15 ++++++ test/pleroma/http/tls_test.exs | 47 +++++++++++++++++++ 6 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 changelog.d/hackney-castore.security create mode 100644 lib/pleroma/http/tls.ex create mode 100644 test/pleroma/http/tls_test.exs diff --git a/changelog.d/hackney-castore.security b/changelog.d/hackney-castore.security new file mode 100644 index 000000000..d30a53f80 --- /dev/null +++ b/changelog.d/hackney-castore.security @@ -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 diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index 2b1502001..fe4df1e1b 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -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 diff --git a/lib/pleroma/http/tls.ex b/lib/pleroma/http/tls.ex new file mode 100644 index 000000000..22718835b --- /dev/null +++ b/lib/pleroma/http/tls.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index 4c81a6225..9026b4586 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -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) diff --git a/test/pleroma/http/adapter_helper/hackney_test.exs b/test/pleroma/http/adapter_helper/hackney_test.exs index 343bdb800..5bf13e25c 100644 --- a/test/pleroma/http/adapter_helper/hackney_test.exs +++ b/test/pleroma/http/adapter_helper/hackney_test.exs @@ -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 diff --git a/test/pleroma/http/tls_test.exs b/test/pleroma/http/tls_test.exs new file mode 100644 index 000000000..7e9a85edd --- /dev/null +++ b/test/pleroma/http/tls_test.exs @@ -0,0 +1,47 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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 From 830455d197957b2b00d92e4f4cbdfc94f92d05fe Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 21 Oct 2026 08:21:43 +0400 Subject: [PATCH 3/6] fix(mailer): keep certificate checks for Swoosh HTTP adapters The TLS version override replaced all Hackney TLS defaults. On OTP 26+ this fails with cacerts: :undefined; on older OTP it skipped certificate checks. Closes #7962 on the public tracker. --- changelog.d/mailer-tls-options.fix | 1 + lib/pleroma/emails/mailer.ex | 4 +++- test/pleroma/emails/mailer_test.exs | 9 +++++++++ test/pleroma/user_test.exs | 4 +++- .../admin_api/controllers/admin_api_controller_test.exs | 4 +++- 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 changelog.d/mailer-tls-options.fix diff --git a/changelog.d/mailer-tls-options.fix b/changelog.d/mailer-tls-options.fix new file mode 100644 index 000000000..fe17eea6d --- /dev/null +++ b/changelog.d/mailer-tls-options.fix @@ -0,0 +1 @@ +Fix Swoosh HTTP mail adapters failing with `cacerts: :undefined` on OTP 26+ diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 2a80f8547..58ef7de94 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -39,7 +39,9 @@ defmodule Pleroma.Emails.Mailer do # temporary hackney fix until hackney max_connections bug is fixed # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 email = - Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + Swoosh.Email.put_private(email, :hackney_options, + ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ) case enabled?() do true -> Swoosh.Mailer.deliver(email, parse_config(config)) diff --git a/test/pleroma/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs index 7a718c653..52b9c0687 100644 --- a/test/pleroma/emails/mailer_test.exs +++ b/test/pleroma/emails/mailer_test.exs @@ -52,4 +52,13 @@ defmodule Pleroma.Emails.MailerTest do subject: "Pleroma test email" ) end + + test "keeps certificate checks for HTTP API adapters" do + Mailer.deliver(@email) + + assert_received {:email, %Swoosh.Email{private: %{hackney_options: hackney_options}}} + + assert hackney_options[:ssl_options] == + Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 63c0c2bb8..f5e6e20fe 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -533,7 +533,9 @@ defmodule Pleroma.UserTest do Pleroma.Emails.UserEmail.account_confirmation_email(registered_user) # temporary hackney fix until hackney max_connections bug is fixed # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 - |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + |> Swoosh.Email.put_private(:hackney_options, + ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ) |> assert_email_sent() end diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index fc578d828..07288caa8 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -1044,7 +1044,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do Pleroma.Emails.UserEmail.account_confirmation_email(first_user) # temporary hackney fix until hackney max_connections bug is fixed # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 - |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + |> Swoosh.Email.put_private(:hackney_options, + ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ) |> assert_email_sent() end From f4ff7cb1eaa23ecffaeb11e8b31417a6db0c4b78 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Wed, 21 Oct 2026 08:21:43 +0400 Subject: [PATCH 4/6] fix(http): make Finch trust the CAStore bundle Mint trusts the OTP CA store, which comes from the OS. Load the CAStore bundle into it when Finch starts, so Finch trusts the same roots as the other adapters. --- changelog.d/hackney-castore.security | 2 +- lib/pleroma/application.ex | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.d/hackney-castore.security b/changelog.d/hackney-castore.security index d30a53f80..44cb90ce6 100644 --- a/changelog.d/hackney-castore.security +++ b/changelog.d/hackney-castore.security @@ -1 +1 @@ -Hackney now trusts only the CAStore certificate bundle instead of its outdated certifi bundle, which still trusted the compromised eMudhra intermediate CAs +Hackney and Finch now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 353e91f72..adc054f3b 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -66,6 +66,7 @@ defmodule Pleroma.Application do if match?({Tesla.Adapter.Finch, _}, adapter) do Logger.info("Starting Finch") + use_castore_for_otp_cacerts() Finch.start_link(name: MyFinch) end @@ -304,6 +305,15 @@ defmodule Pleroma.Application do end end + # Mint trusts the OTP CA store, which comes from the OS. Make it the CAStore + # bundle that the other adapters trust. OTP 24 has no such store and Mint + # uses CAStore there anyway. + defp use_castore_for_otp_cacerts do + if Code.ensure_loaded?(:public_key) and function_exported?(:public_key, :cacerts_load, 1) do + :ok = :public_key.cacerts_load(CAStore.file_path()) + end + end + defp http_children_hackney do pools = [:federation, :media] From 92a112cf15dc73a7ac88737c4d407745e47db89d Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 22 Oct 2026 08:48:56 +0400 Subject: [PATCH 5/6] fix(mailer): drop the TLS 1.2 pin The pin worked around a TLS 1.3 problem with Hackney on OTP 23 (swoosh/swoosh#478). Pleroma no longer supports OTP 23. --- lib/pleroma/emails/mailer.ex | 5 ++--- test/pleroma/emails/mailer_test.exs | 2 +- test/pleroma/user_test.exs | 4 +--- .../web/admin_api/controllers/admin_api_controller_test.exs | 4 +--- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 58ef7de94..fa6fd8020 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -36,11 +36,10 @@ defmodule Pleroma.Emails.Mailer do def deliver(email, config \\ []) def deliver(email, config) do - # temporary hackney fix until hackney max_connections bug is fixed - # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 + # Swoosh HTTP adapters use Hackney, which otherwise trusts its certifi bundle email = Swoosh.Email.put_private(email, :hackney_options, - ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ssl_options: Pleroma.HTTP.TLS.ssl_options() ) case enabled?() do diff --git a/test/pleroma/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs index 52b9c0687..2c2b5a4af 100644 --- a/test/pleroma/emails/mailer_test.exs +++ b/test/pleroma/emails/mailer_test.exs @@ -59,6 +59,6 @@ defmodule Pleroma.Emails.MailerTest do assert_received {:email, %Swoosh.Email{private: %{hackney_options: hackney_options}}} assert hackney_options[:ssl_options] == - Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + Pleroma.HTTP.TLS.ssl_options() end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index f5e6e20fe..ec5a2a12a 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -531,10 +531,8 @@ defmodule Pleroma.UserTest do ObanHelpers.perform_all() Pleroma.Emails.UserEmail.account_confirmation_email(registered_user) - # temporary hackney fix until hackney max_connections bug is fixed - # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 |> Swoosh.Email.put_private(:hackney_options, - ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ssl_options: Pleroma.HTTP.TLS.ssl_options() ) |> assert_email_sent() end diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index 07288caa8..aa38a7454 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -1042,10 +1042,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do ObanHelpers.perform_all() Pleroma.Emails.UserEmail.account_confirmation_email(first_user) - # temporary hackney fix until hackney max_connections bug is fixed - # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 |> Swoosh.Email.put_private(:hackney_options, - ssl_options: Pleroma.HTTP.TLS.ssl_options(versions: [:"tlsv1.2"]) + ssl_options: Pleroma.HTTP.TLS.ssl_options() ) |> assert_email_sent() end From 65076aa2f88a9ba25adc974a586b7d6556135d45 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Thu, 22 Oct 2026 10:05:12 +0400 Subject: [PATCH 6/6] fix(http): reject Hackney's certifi partial chains Hackney 4 merges given ssl_options into its defaults and keeps its own partial_chain, which trusts any certificate in the peer chain that is in certifi. A peer could send the emSign ECC Root CA - G3 root in its chain and pass. Set partial_chain so certifi is never a trust anchor. --- lib/pleroma/http/tls.ex | 12 +++++++++--- test/pleroma/http/tls_test.exs | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/http/tls.ex b/lib/pleroma/http/tls.ex index 22718835b..e1fbc7caa 100644 --- a/lib/pleroma/http/tls.ex +++ b/lib/pleroma/http/tls.ex @@ -6,9 +6,11 @@ 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 + Hackney otherwise trusts its own certifi bundle. Hackney 1 drops all of its default TLS options, including certificate checks, as soon as any - `ssl_options` are given. + `ssl_options` are given. Hackney 4 merges them, so `partial_chain` must be + set too: Hackney's own one trusts any certificate in the peer chain that is + in certifi. """ @spec ssl_options(keyword()) :: keyword() @@ -18,12 +20,16 @@ defmodule Pleroma.HTTP.TLS do verify: :verify_peer, cacertfile: CAStore.file_path(), depth: 20, - customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)] + customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)], + partial_chain: &__MODULE__.reject_partial_chain/1 ], overrides ) end + @doc false + def reject_partial_chain(_certs), do: :unknown_ca + @spec put_hackney_ssl_options(keyword()) :: keyword() def put_hackney_ssl_options(opts) do if opts[:insecure] do diff --git a/test/pleroma/http/tls_test.exs b/test/pleroma/http/tls_test.exs index 7e9a85edd..490950b50 100644 --- a/test/pleroma/http/tls_test.exs +++ b/test/pleroma/http/tls_test.exs @@ -17,6 +17,12 @@ defmodule Pleroma.HTTP.TLSTest do assert is_function(match_fun, 2) end + test "does not accept certificates from the peer chain as trust anchors" do + partial_chain = TLS.ssl_options()[:partial_chain] + + assert partial_chain.([:certificate]) == :unknown_ca + end + test "keeps the CA bundle when other options are given" do opts = TLS.ssl_options(versions: [:"tlsv1.2"])