Now JWT_LOADER takes the alg as argument, to avoid security issue where the lib is taking alg from the header (which may be a bad security weakness).
This commit is contained in:
36
library/security/jwt/src/errors/jwt_mismatched_alg_error.e
Normal file
36
library/security/jwt/src/errors/jwt_mismatched_alg_error.e
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
note
|
||||||
|
description: "Summary description for {JWT_MISMATCHED_ALG_ERROR}."
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
JWT_MISMATCHED_ALG_ERROR
|
||||||
|
|
||||||
|
inherit
|
||||||
|
JWT_ERROR
|
||||||
|
|
||||||
|
create
|
||||||
|
make
|
||||||
|
|
||||||
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
|
make (a_alg, a_header_alg: READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
alg := a_alg
|
||||||
|
header_alg := a_header_alg
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
alg: READABLE_STRING_8
|
||||||
|
|
||||||
|
header_alg: READABLE_STRING_8
|
||||||
|
|
||||||
|
id: STRING = "ALG_MISMATCH"
|
||||||
|
|
||||||
|
message: READABLE_STRING_8
|
||||||
|
do
|
||||||
|
Result := "Header alg [" + header_alg + "] does not match given alg [" + alg + "]!"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -118,6 +118,11 @@ feature {JWT_UTILITIES} -- Error reporting
|
|||||||
l_errors.extend (err)
|
l_errors.extend (err)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
report_mismatched_alg_error (alg, a_header_alg: READABLE_STRING_8)
|
||||||
|
do
|
||||||
|
report_error (create {JWT_MISMATCHED_ALG_ERROR}.make (alg, a_header_alg))
|
||||||
|
end
|
||||||
|
|
||||||
report_unsupported_alg_error (alg: READABLE_STRING_8)
|
report_unsupported_alg_error (alg: READABLE_STRING_8)
|
||||||
do
|
do
|
||||||
report_error (create {JWT_UNSUPPORTED_ALG_ERROR}.make (alg))
|
report_error (create {JWT_UNSUPPORTED_ALG_ERROR}.make (alg))
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
note
|
note
|
||||||
description: "Summary description for {JWT_LOADER}."
|
description: "Loader and verifier to JWT token."
|
||||||
author: ""
|
|
||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
EIS: "name=Known Critical vulnerabilities in JWT libs", "protocol=URI", "src=https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/"
|
||||||
|
|
||||||
class
|
class
|
||||||
JWT_LOADER
|
JWT_LOADER
|
||||||
@@ -12,9 +12,13 @@ inherit
|
|||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
token (a_token_input: READABLE_STRING_8; a_secret: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
|
token (a_token_input: READABLE_STRING_8; a_alg: detachable READABLE_STRING_8; a_verification_key: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
|
||||||
-- Decoded token from `a_token_input` given the secret `a_secret`, and optional context `ctx`
|
-- Decoded token from `a_token_input` given the verification key `a_verification_key` and optional (but recommended) signature algorithm `a_alg`, and optional context `ctx`
|
||||||
-- used to specify eventual issuer and various parameters.
|
-- used to specify eventual issuer and various parameters.
|
||||||
|
-- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens,
|
||||||
|
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
|
||||||
|
require
|
||||||
|
a_valid_alg: a_alg /= Void implies is_supporting_signature_algorithm (a_alg)
|
||||||
local
|
local
|
||||||
jws: JWS
|
jws: JWS
|
||||||
i,j,n: INTEGER
|
i,j,n: INTEGER
|
||||||
@@ -29,20 +33,27 @@ feature -- Access
|
|||||||
l_enc_payload := a_token_input.substring (i + 1, j - 1)
|
l_enc_payload := a_token_input.substring (i + 1, j - 1)
|
||||||
l_signature := a_token_input.substring (j + 1, n)
|
l_signature := a_token_input.substring (j + 1, n)
|
||||||
create jws.make_with_json_payload (base64url_decode (l_enc_payload))
|
create jws.make_with_json_payload (base64url_decode (l_enc_payload))
|
||||||
|
|
||||||
alg := signature_algorithm_from_encoded_header (l_enc_header)
|
alg := signature_algorithm_from_encoded_header (l_enc_header)
|
||||||
jws.set_algorithm (alg)
|
if a_alg /= Void then
|
||||||
|
if alg /= Void and then not alg.is_case_insensitive_equal_general (a_alg) then
|
||||||
|
jws.report_mismatched_alg_error (a_alg, alg)
|
||||||
|
else
|
||||||
|
alg := a_alg
|
||||||
|
end
|
||||||
|
else
|
||||||
if alg = Void then
|
if alg = Void then
|
||||||
-- Use default
|
-- Use default
|
||||||
alg := alg_hs256
|
alg := alg_hs256
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
jws.set_algorithm (alg)
|
||||||
check alg_set: alg /= Void end
|
check alg_set: alg /= Void end
|
||||||
if ctx = Void or else not ctx.validation_ignored then
|
if ctx = Void or else not ctx.validation_ignored then
|
||||||
if not is_supporting_signature_algorithm (alg) then
|
if not is_supporting_signature_algorithm (alg) then
|
||||||
jws.report_unsupported_alg_error (alg)
|
jws.report_unsupported_alg_error (alg)
|
||||||
alg := alg_hs256
|
alg := alg_hs256
|
||||||
end
|
end
|
||||||
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
|
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg)) then
|
||||||
jws.report_unverified_token_error
|
jws.report_unverified_token_error
|
||||||
end
|
end
|
||||||
if
|
if
|
||||||
|
|||||||
@@ -54,7 +54,14 @@ feature -- Test
|
|||||||
|
|
||||||
create jwt_loader
|
create jwt_loader
|
||||||
|
|
||||||
if attached jwt_loader.token (tok, "secret", Void) as l_tok then
|
-- Use header alg!
|
||||||
|
if attached jwt_loader.token (tok, Void, "secret", Void) as l_tok then
|
||||||
|
assert ("no error", not l_tok.has_error)
|
||||||
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use given alg!
|
||||||
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
@@ -96,21 +103,21 @@ feature -- Test
|
|||||||
create jwt_loader
|
create jwt_loader
|
||||||
|
|
||||||
-- Test with validation + exp
|
-- Test with validation + exp
|
||||||
if attached jwt_loader.token (tok, "secret", Void) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
|
|
||||||
create ctx
|
create ctx
|
||||||
ctx.set_time (now)
|
ctx.set_time (now)
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
dt := duplicated_time (now)
|
dt := duplicated_time (now)
|
||||||
dt.hour_add (5)
|
dt.hour_add (5)
|
||||||
ctx.set_time (dt)
|
ctx.set_time (dt)
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("exp error", l_tok.has_error)
|
assert ("exp error", l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -122,7 +129,7 @@ feature -- Test
|
|||||||
tok := jwt.encoded_string ("secret")
|
tok := jwt.encoded_string ("secret")
|
||||||
|
|
||||||
ctx.set_time (now)
|
ctx.set_time (now)
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("has nbf error", l_tok.has_error)
|
assert ("has nbf error", l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ feature -- Test
|
|||||||
dt.second_add (15)
|
dt.second_add (15)
|
||||||
ctx.set_time (dt)
|
ctx.set_time (dt)
|
||||||
|
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("has nbf error", l_tok.has_error)
|
assert ("has nbf error", l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,31 +145,51 @@ feature -- Test
|
|||||||
dt.minute_add (45)
|
dt.minute_add (45)
|
||||||
ctx.set_time (dt)
|
ctx.set_time (dt)
|
||||||
|
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test Issuer
|
-- Test Issuer
|
||||||
ctx.set_issuer ("urn:foobar")
|
ctx.set_issuer ("urn:foobar")
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("has iss error", l_tok.has_error)
|
assert ("has iss error", l_tok.has_error)
|
||||||
end
|
end
|
||||||
ctx.set_issuer ("urn:foo")
|
ctx.set_issuer ("urn:foo")
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test Audience
|
-- Test Audience
|
||||||
ctx.set_audience ("urn:foobar")
|
ctx.set_audience ("urn:foobar")
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("has aud error", l_tok.has_error)
|
assert ("has aud error", l_tok.has_error)
|
||||||
end
|
end
|
||||||
ctx.set_audience ("urn:foo")
|
ctx.set_audience ("urn:foo")
|
||||||
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
|
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||||
assert ("no error", not l_tok.has_error)
|
assert ("no error", not l_tok.has_error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test_mismatched_alg_jwt
|
||||||
|
local
|
||||||
|
jwt: JWS
|
||||||
|
payload: STRING
|
||||||
|
tok: STRING
|
||||||
|
do
|
||||||
|
payload := "[
|
||||||
|
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
|
||||||
|
]"
|
||||||
|
|
||||||
|
create jwt.make_with_json_payload (payload)
|
||||||
|
jwt.set_algorithm ("none")
|
||||||
|
tok := jwt.encoded_string ("secret")
|
||||||
|
|
||||||
|
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
|
||||||
|
assert ("no error", not jwt.has_error)
|
||||||
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test_unsecured_jwt
|
test_unsecured_jwt
|
||||||
local
|
local
|
||||||
jwt: JWS
|
jwt: JWS
|
||||||
@@ -177,7 +204,11 @@ feature -- Test
|
|||||||
jwt.set_algorithm ("none")
|
jwt.set_algorithm ("none")
|
||||||
tok := jwt.encoded_string ("secret")
|
tok := jwt.encoded_string ("secret")
|
||||||
|
|
||||||
if attached (create {JWT_LOADER}).token (tok, "secret", Void) as l_tok then
|
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
|
||||||
|
assert ("no error", not jwt.has_error)
|
||||||
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
|
end
|
||||||
|
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
|
||||||
assert ("no error", not jwt.has_error)
|
assert ("no error", not jwt.has_error)
|
||||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user