diff --git a/lib/plug/ssl.ex b/lib/plug/ssl.ex index f48a3bf3..7dc97e9c 100644 --- a/lib/plug/ssl.ex +++ b/lib/plug/ssl.ex @@ -301,10 +301,25 @@ defmodule Plug.SSL do end defp set_strong_tls_defaults(options) do - options - |> set_managed_tls_defaults - |> keynew(:ciphers, 0, {:ciphers, @strong_tls_ciphers}) - |> keynew(:versions, 0, {:versions, [:"tlsv1.3"]}) + options = + options + |> set_managed_tls_defaults + |> keynew(:ciphers, 0, {:ciphers, @strong_tls_ciphers}) + |> keynew(:versions, 0, {:versions, [:"tlsv1.3"]}) + + # secure_renegotiate and reuse_sessions are TLS 1.2 and earlier options. + # They must be removed for TLS 1.3-only configurations because OTP 28+ + # validates that these options are not set when only TLS 1.3 is enabled. + # Only remove them if the final versions list has no pre-TLS 1.3 versions. + versions = options[:versions] + + if Enum.any?([:tlsv1, :"tlsv1.1", :"tlsv1.2"], &(&1 in versions)) do + options + else + options + |> List.keydelete(:secure_renegotiate, 0) + |> List.keydelete(:reuse_sessions, 0) + end end defp set_compatible_tls_defaults(options) do diff --git a/test/plug/ssl_test.exs b/test/plug/ssl_test.exs index 733e5f7b..ae6d60c9 100644 --- a/test/plug/ssl_test.exs +++ b/test/plug/ssl_test.exs @@ -42,6 +42,10 @@ defmodule Plug.SSLTest do assert opts[:honor_cipher_order] == true assert opts[:eccs] == [:x25519, :secp256r1, :secp384r1, :secp521r1] assert opts[:versions] == [:"tlsv1.3"] + # secure_renegotiate and reuse_sessions must NOT be set for TLS 1.3-only + # configurations, as OTP 28+ rejects these options with TLS 1.3 + assert opts[:secure_renegotiate] == nil + assert opts[:reuse_sessions] == nil assert opts[:ciphers] == [ ~c"TLS_AES_256_GCM_SHA384", @@ -50,6 +54,21 @@ defmodule Plug.SSLTest do ] end + test "sets cipher suite to strong but preserves secure_renegotiate when TLS 1.2 is included" do + # When user overrides versions to include TLS 1.2, secure_renegotiate should be preserved + assert {:ok, opts} = + configure( + key: "abcdef", + cert: "ghijkl", + cipher_suite: :strong, + versions: [:"tlsv1.3", :"tlsv1.2"] + ) + + assert opts[:versions] == [:"tlsv1.3", :"tlsv1.2"] + assert opts[:secure_renegotiate] == true + assert opts[:reuse_sessions] == true + end + test "sets cipher suite to compatible" do assert {:ok, opts} = configure(key: "abcdef", cert: "ghijkl", cipher_suite: :compatible) assert opts[:cipher_suite] == nil