Skip to content

Commit 0ce993d

Browse files
authored
fix: HS256 JWK signature verification (#1467)
1 parent 08e6ef6 commit 0ce993d

File tree

4 files changed

+95
-16
lines changed

4 files changed

+95
-16
lines changed

lib/realtime_web/channels/auth/jwt_verification.ex

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,17 @@ defmodule RealtimeWeb.JwtVerification do
107107
"keys" => keys
108108
})
109109
when is_binary(kid) and alg in @hs_algorithms do
110-
jwk = Enum.find(keys, fn jwk -> jwk["kty"] == "oct" and jwk["kid"] == kid end)
110+
jwk = Enum.find(keys, fn jwk -> jwk["kty"] == "oct" and jwk["kid"] == kid and is_binary(jwk["k"]) end)
111111

112-
case jwk do
112+
if jwk do
113+
case Base.url_decode64(jwk["k"], padding: false) do
114+
{:ok, secret} -> {:ok, Joken.Signer.create(alg, secret)}
115+
_ -> {:error, :error_generating_signer}
116+
end
117+
else
113118
# If there's no JWK, and HS* is being used, instead of erroring, try
114119
# the jwt_secret instead.
115-
nil -> {:ok, Joken.Signer.create(alg, jwt_secret)}
116-
_ -> {:ok, Joken.Signer.create(alg, jwk)}
120+
{:ok, Joken.Signer.create(alg, jwt_secret)}
117121
end
118122
end
119123

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Realtime.MixProject do
44
def project do
55
[
66
app: :realtime,
7-
version: "2.41.2",
7+
version: "2.41.3",
88
elixir: "~> 1.17.3",
99
elixirc_paths: elixirc_paths(Mix.env()),
1010
start_permanent: Mix.env() == :prod,

test/realtime_web/channels/auth/jwt_verification_test.exs

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ defmodule RealtimeWeb.JwtVerificationTest do
1111
setup_all do
1212
Application.put_env(:realtime, :jwt_secret, @jwt_secret)
1313
Application.put_env(:realtime, :jwt_claim_validators, %{})
14-
on_exit(fn -> Application.put_env(:realtime, :jwt_claim_validators, %{}) end)
1514
:ok
1615
end
1716

1817
setup do
1918
start_supervised(Mock)
19+
on_exit(fn -> Application.put_env(:realtime, :jwt_claim_validators, %{}) end)
2020
:ok
2121
end
2222

@@ -218,7 +218,26 @@ defmodule RealtimeWeb.JwtVerificationTest do
218218
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfNnUI5w"
219219

220220
# Check that the signature is valid even though time may be off.
221-
assert {:error, :signature_error} != JwtVerification.verify(token, @jwt_secret, jwks)
221+
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
222+
end
223+
224+
test "using RS256 JWK but wrong signature" do
225+
jwks = %{
226+
"keys" => [
227+
%{
228+
"kty" => "RSA",
229+
"n" =>
230+
"6r1mKwCalvJ0NyThyQkBr5huFILwwhXcxtsdlw-WybNz4avzODQwLFkA-b2fnnfdFgualV2NdcvoJSo1bzVGCWWqwWKWdTQKFjtcjAIC4FnhOv5ynNF9Ub-11ORDd1aiq_4XKNA4GaS1HqBekVDAAvJYy99Jz0CkLx4NU_VrS0U9sOQzUAhy2MwZCx2kZ3SWKEMjjEIkbvIb22IdRTyuFsAndKGpyzhw-MalnU5P2hOig-QApNBc0WJtTHTAa4PLQ6v_5jNc5PzCwP8jGK9SlrSF-GOnx9BVBX9t-AIDp-BviKbtY7y-pku6-f7HSiS2T3iAJkHXPm9E_NwwhWzMJQ",
231+
"e" => "AQAB",
232+
"kid" => "key-id-1"
233+
}
234+
]
235+
}
236+
237+
token =
238+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDc1NjUsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MTE2NX0.zUeoZrWK1efAc4q9y978_9qkhdXktdjf5H8O9Rw0SHcPaXW8OBcuNR2huRrgORvqFx6_sHn6nCJaWkZGzO-f8wskMD7Z4INq2JUypr6nASie3Qu2lLyeY3WTInaXNAKH-oqlfTLRskbz8zkIxOj2bBJiN9ceQLkJU-c92ndiuiG5D1jyQrGsvRdFem_cemp0yOoEaC0XWdjeV6C_UD-34GIyv3o8H4HZg1GcCiyNnAfDmLAcTOQPmqkwsRDQb-pm5O3HwpQt9WHOB6i1vzf-nmIGyCRA7STPdALK16-aiAyT4SJRxM5WN3iK8yitH7g4JETb9WocBbwIM_zfnnUI5w"
239+
240+
assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
222241
end
223242

224243
test "using ES256 JWK" do
@@ -239,7 +258,68 @@ defmodule RealtimeWeb.JwtVerificationTest do
239258
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHRxw"
240259

241260
# Check that the signature is valid even though time may be off.
242-
assert {:error, :signature_error} != JwtVerification.verify(token, @jwt_secret, jwks)
261+
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
262+
end
263+
264+
test "using ES256 JWK with wrong signature" do
265+
jwks = %{
266+
"keys" => [
267+
%{
268+
"kty" => "EC",
269+
"x" => "iX_niXPSL2nW-9IyCELzyceAtuE3B98pWML5tQGACD4",
270+
"y" => "kT02DoLhXx6gtpkbrN8XwQ2wtzE6cDBaqlWgVXIeqV0",
271+
"crv" => "P-256",
272+
"d" => "FBVYnsYA2C3FTggEwV8kCRMo4FLl220_cWY2RdXyb_8",
273+
"kid" => "key-id-1"
274+
}
275+
]
276+
}
277+
278+
token =
279+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1pZC0xIn0.eyJpYXQiOjE3MTIwNDk2NTcsInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoidXNlci1pZCIsImV4cCI6MTcxMjA1MzI1N30.IIQBuEiSnZacGMqiqsrLAeRGOjIaB4F3x1gnLN5zvhFryJ-6tdgu96lFv5HUF13IL2UfHWad0OuvoDt4DEHrxw"
280+
281+
assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
282+
end
283+
284+
test "using HS256 JWK" do
285+
jwks = %{
286+
"keys" => [
287+
%{
288+
"alg" => "HS256",
289+
"k" =>
290+
"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ",
291+
"key_ops" => ["verify"],
292+
"kid" => "4FcGwlBxkBV1bSZw",
293+
"kty" => "oct"
294+
}
295+
]
296+
}
297+
298+
token =
299+
"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyAPxoDwk"
300+
301+
# Check that the signature is valid even though time may be off.
302+
assert JwtVerification.verify(token, @jwt_secret, jwks) != {:error, :signature_error}
303+
end
304+
305+
test "using HS256 JWK with wrong signature" do
306+
jwks = %{
307+
"keys" => [
308+
%{
309+
"alg" => "HS256",
310+
"k" =>
311+
"WWpiUEVXK2I4dVM1djkzMS9TWTNmb2RtcUtiZVh3NnBHS0JaS1JDMGpaODdhVHpaZ3N0Ly9yMG0wU1M4Z1U4OFE0aGdwclBMMzVRRU5ya253TWxhUlE9PQ",
312+
"key_ops" => ["verify"],
313+
"kid" => "4FcGwlBxkBV1bSZw",
314+
"kty" => "oct"
315+
}
316+
]
317+
}
318+
319+
token =
320+
"eyJhbGciOiJIUzI1NiIsImtpZCI6IjRGY0d3bEJ4a0JWMWJTWnciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2hqbmRnYWdpZGlwY3RxdXFxeXloLnN1cGFiYXNlLmNvL2F1dGgvdjEiLCJzdWIiOiJmZjA0NjVlMy1lZjk3LTRkYjItOWE1Zi0zZDI4Y2YxODE0MmYiLCJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzUyODA4NjE4LCJpYXQiOjE3NTI4MDUwMTgsImVtYWlsIjoiY2hhdEBlZHVhcmRvLmd1cmdlbC5tZSIsInBob25lIjoiIiwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwiLCJwcm92aWRlcnMiOlsiZW1haWwiXX0sInVzZXJfbWV0YWRhdGEiOnsiZW1haWwiOiJjaGF0QGVkdWFyZG8uZ3VyZ2VsLm1lIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInBob25lX3ZlcmlmaWVkIjpmYWxzZSwic3ViIjoiZmYwNDY1ZTMtZWY5Ny00ZGIyLTlhNWYtM2QyOGNmMTgxNDJmIn0sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwiYWFsIjoiYWFsMSIsImFtciI6W3sibWV0aG9kIjoicGFzc3dvcmQiLCJ0aW1lc3RhbXAiOjE3NTI4MDUwMTh9XSwic2Vzc2lvbl9pZCI6IjA2MDJkYWM0LWMwMjctNGIwNi1hZDM5LTMzN2ViMTZlODdlNSIsImlzX2Fub255bW91cyI6ZmFsc2V9.SnGzRjLfHPtT64kXYEQVBLKizCl76LqEPILyApxoDwk"
321+
322+
assert JwtVerification.verify(token, @jwt_secret, jwks) == {:error, :signature_error}
243323
end
244324

245325
test "returns error when no matching JWK is found for RSA algorithm" do

test/realtime_web/channels/realtime_channel/broadcast_handler_test.exs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,10 @@ defmodule RealtimeWeb.RealtimeChannel.BroadcastHandlerTest do
266266

267267
log =
268268
capture_log(fn ->
269-
for _ <- 1..100, reduce: socket do
270-
socket ->
271-
{:noreply, socket} = BroadcastHandler.handle(%{}, db_conn, socket)
272-
socket
273-
end
269+
{:noreply, _socket} = BroadcastHandler.handle(%{}, db_conn, socket)
274270

275-
Process.sleep(1200)
276-
277-
refute_received _
271+
# Enough for the RateCounter to calculate the last bucket
272+
refute_received _, 1200
278273
end)
279274

280275
assert log =~ "RlsPolicyError"

0 commit comments

Comments
 (0)