Skip to content

Commit

Permalink
Merge pull request #1371 from 3scale/THREESCALE-8601-jwk-alg-matcher
Browse files Browse the repository at this point in the history
THREESCALE-8601 jwt alg verification allows no alg in jwk object
  • Loading branch information
kevprice83 authored Aug 17, 2022
2 parents 4890f6a + 9d158c4 commit 703e402
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 5 deletions.
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

0 comments on commit 703e402

Please sign in to comment.