Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

THREESCALE-8601 jwt alg verification allows no alg in jwk object #1371

Merged
merged 2 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Fixed issue where request path is stripped for proxied https requests [PR #1342](https://github.com/3scale/APIcast/pull/1342) [THREESCALE-8426](https://issues.redhat.com/browse/THREESCALE-8426)
- Bumped liquid-lua to version 0.2.0-2 [PR #1369](https://github.com/3scale/APIcast/pull/1369) - includes: [THREESCALE-8483](https://issues.redhat.com/browse/THREESCALE-8483) and [THREESCALE-8484](https://issues.redhat.com/browse/THREESCALE-8484)
- Fixed: APIcast could not retrieve the latest version of the proxy config [PR #1370](https://github.com/3scale/APIcast/pull/1370) [THREESCALE-8485](https://issues.redhat.com/browse/THREESCALE-8485)
- Fixed: JWKs without alg field cause the JWT validation process to fail [PR #1371](https://github.com/3scale/APIcast/pull/1371) [THREESCALE-8601](https://issues.redhat.com/browse/THREESCALE-8601)

### Added

Expand Down
4 changes: 3 additions & 1 deletion gateway/src/apicast/oauth/oidc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ function _M:verify(jwt, cache_key)
local jwk_obj = find_jwk(jwt, self.keys)

local pubkey = jwk_obj.pem
if jwk_obj.alg ~= jwt.header.alg then
-- Check the jwk for the alg field and if not present skip the validation as it is
-- OPTIONAL according to https://www.rfc-editor.org/rfc/rfc7517#section-4.4
if jwk_obj.alg and jwk_obj.alg ~= jwt.header.alg then
return false, '[jwt] alg mismatch'
end

Expand Down
24 changes: 24 additions & 0 deletions spec/oauth/oidc_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ describe('OIDC', function()
config = { id_token_signing_alg_values_supported = { 'RS256', 'HS256' } },
keys = { somekid = { pem = rsa.pub, alg = 'RS256' } },
}
local oidc_config_no_alg = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'RS256', 'HS256' } },
keys = { somekid = { pem = rsa.pub } },
}

before_each(function() jwt_validators.set_system_clock(function() return 0 end) end)

Expand Down Expand Up @@ -268,6 +273,25 @@ describe('OIDC', function()
assert(credentials, err)
end)

it('validation passes when jwk.alg does not exist', function()
local oidc = _M.new(oidc_config_no_alg)
local access_token = jwt:sign(rsa.private, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
aud = 'notused',
azp = 'ce3b2e5e',
sub = 'someone',
nbf = 0,
exp = ngx.now() + 10,
typ = 'Bearer'
},
})

local credentials, _, _, err = oidc:transform_credentials({ access_token = access_token })
assert(credentials, err)
end)

describe('getting client_id from any JWT claim', function()

before_each(function()
Expand Down
55 changes: 51 additions & 4 deletions t/apicast-oidc.t
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,7 @@ my $jwt = encode_jwt(payload => {
--- no_error_log
[error]



=== TEST 2: JWT verification fails when no alg is present in the jwk to match against jwt.header.alg
=== TEST 6: JWT verification does not fail when no alg is present in the jwk to match against jwt.header.alg
--- configuration env eval
use JSON qw(to_json);

Expand Down Expand Up @@ -303,7 +301,7 @@ to_json({
}
}
--- request: GET /test
--- error_code: 403
--- error_code: 200
--- more_headers eval
use Crypt::JWT qw(encode_jwt);
my $jwt = encode_jwt(payload => {
Expand All @@ -313,5 +311,54 @@ my $jwt = encode_jwt(payload => {
iss => 'https://example.com/auth/realms/apicast',
exp => time + 3600 }, key => \$::private_key, alg => 'RS256', extra_headers => { kid => 'somekid' });
"Authorization: Bearer $jwt"
--- no_error_log

=== TEST 7: JWT verification fails when jwk.alg exists AND does not match jwt.header.alg
(see THREESCALE-8249 for steps to generate tampered JWT. rsa.pub from fixtures used to sign)
--- configuration env eval
use JSON qw(to_json);

to_json({
services => [{
id => 42,
backend_version => 'oauth',
backend_authentication_type => 'provider_key',
backend_authentication_value => 'fookey',
proxy => {
authentication_method => 'oidc',
oidc_issuer_endpoint => 'https://example.com/auth/realms/apicast',
api_backend => "http://test:$TEST_NGINX_SERVER_PORT/",
proxy_rules => [
{ pattern => '/', http_method => 'GET', metric_system_name => 'hits', delta => 1 }
]
}
}],
oidc => [{
issuer => 'https://example.com/auth/realms/apicast',
config => { id_token_signing_alg_values_supported => [ 'RS256', 'HS256' ] },
keys => { somekid => { pem => $::public_key, alg => 'RS256' } },
}]
});
--- upstream
location /test {
echo "yes";
}
--- backend
location = /transactions/oauth_authrep.xml {
content_by_lua_block {
local expected = "provider_key=fookey&service_id=42&usage%5Bhits%5D=1&app_id=appid"
require('luassert').same(ngx.decode_args(expected), ngx.req.get_uri_args(0))
}
}
--- request: GET /test
--- error_code: 403
--- more_headers eval
use Crypt::JWT qw(encode_jwt);
my $jwt = 'eyJraWQiOiJzb21la2lkIiwiYWxnIjoiSFMyNTYifQ.'.
'eyJleHAiOjcxNzA1MzE2NDMwLCJhenAiOiJhcHBpZCIsInN1YiI6In'.
'NvbWVvbmUiLCJhdWQiOiJzb21ldGhpbmciLCJpc3MiOiJodHRwczov'.
'L2V4YW1wbGUuY29tL2F1dGgvcmVhbG1zL2FwaWNhc3QifQ.1rFq5QN'.
'b99W6aqQjsx7GJGLDpdkDLI6-huZLzMAmxGQ';
"Authorization: Bearer $jwt"
--- error_log
[jwt] alg mismatch