From a87b09454c5d11ff5c12952bc56ee794689ae3f0 Mon Sep 17 00:00:00 2001 From: Matt Ludwigs Date: Tue, 24 Jun 2025 20:33:26 -0700 Subject: [PATCH 1/2] Reject tokens with future signed_at timestamps --- lib/plug/crypto.ex | 26 +++++++++++++++++++------- test/plug/crypto_test.exs | 5 +++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/plug/crypto.ex b/lib/plug/crypto.ex index 682ec05..43a95cc 100644 --- a/lib/plug/crypto.ex +++ b/lib/plug/crypto.ex @@ -333,10 +333,9 @@ defmodule Plug.Crypto do %{data: data, signed: signed} -> {data, signed, 86400} end - if expired?(signed, Keyword.get(opts, :max_age, max_age)) do - {:error, :expired} - else - {:ok, data} + case validate_age(signed, Keyword.get(opts, :max_age, max_age)) do + :ok -> {:ok, data} + error -> error end end @@ -351,9 +350,22 @@ defmodule Plug.Crypto do KeyGenerator.generate(secret_key_base, salt, iterations, length, digest, cache) end - defp expired?(_signed, :infinity), do: false - defp expired?(_signed, max_age_secs) when max_age_secs <= 0, do: true - defp expired?(signed, max_age_secs), do: signed + trunc(max_age_secs * 1000) < now_ms() + defp validate_age(_signed, :infinity), do: :ok + defp validate_age(_signed, max_age_secs) when max_age_secs <= 0, do: {:error, :expired} + + defp validate_age(signed, max_age_secs) do + now = now_ms() + + if signed > now do + {:error, :invalid} + else + if signed + trunc(max_age_secs * 1000) < now do + {:error, :expired} + else + :ok + end + end + end defp now_ms, do: System.os_time(:millisecond) end diff --git a/test/plug/crypto_test.exs b/test/plug/crypto_test.exs index 95d2bf5..1811ca1 100644 --- a/test/plug/crypto_test.exs +++ b/test/plug/crypto_test.exs @@ -200,6 +200,11 @@ defmodule Plug.CryptoTest do {:error, :expired} end + test "ensures signed_at is not in future while decrypting" do + token = encrypt(@key, "secret", 1, signed_at: System.os_time(:second) + 31_536_000) + assert {:error, :invalid} = decrypt(@key, "secret", token) + end + test "passes key_iterations options to key generator" do signed1 = encrypt(@key, "secret", 1, signed_at: 0, key_iterations: 1) signed2 = encrypt(@key, "secret", 1, signed_at: 0, key_iterations: 2) From fb96a38c98c8156e3542fc35b6fe90498f653666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 25 Jun 2025 10:23:14 +0200 Subject: [PATCH 2/2] Update lib/plug/crypto.ex --- lib/plug/crypto.ex | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/plug/crypto.ex b/lib/plug/crypto.ex index 43a95cc..6208fe5 100644 --- a/lib/plug/crypto.ex +++ b/lib/plug/crypto.ex @@ -356,14 +356,10 @@ defmodule Plug.Crypto do defp validate_age(signed, max_age_secs) do now = now_ms() - if signed > now do - {:error, :invalid} - else - if signed + trunc(max_age_secs * 1000) < now do - {:error, :expired} - else - :ok - end + cond do + signed > now -> {:error, :invalid} + signed + trunc(max_age_secs * 1000) < now -> {:error, :expired} + true -> :ok end end