opa: JWKS validation cannot be configured to enforce that a token is considered valid, if any only if the token's signature can be validated using the key with the kid provided in the token
Assumption: The token validation is configured to use a JSON Web Key Set (JWKS) for signature validation.
Current behavior (tested with Opa 0.32.1): If the token’s signature can be validated with any public key provided in the JWKS, then the token’s signature considered to be valid. This is acceptable with RFC 7515 see Appendix D. Notes on Key Selection (https://datatracker.ietf.org/doc/html/rfc7515#appendix-D).
Appendix D. Notes on Key Selection
...
2. Filter the set of collected keys. For instance, some
applications will use only keys referenced by "kid" (key ID) or
"x5t" (X.509 certificate SHA-1 thumbprint) parameters. If the
application uses the JWK "alg" (algorithm), "use" (public key
use), or "key_ops" (key operations) parameters, keys with
inappropriate values of those parameters would be excluded.
Additionally, keys might be filtered to include or exclude keys
with certain other member values in an application-specific
manner. For some applications, no filtering will be applied.
3. Order the set of collected keys. For instance, keys referenced
by "kid" (key ID) or "x5t" (X.509 certificate SHA-1 thumbprint)
parameters might be tried before keys with neither of these
values. Likewise, keys with certain member values might be
ordered before keys with other member values. For some
applications, no ordering will be applied.
...
Requested behavior: Support to configure a “strict” token validation as follows: When using a JWKS for token validation, then a token’s signature must only considered to be valid, if and only if the token’s signature can be validated using the public key from the JWKS which as the same “kid” as the “kid” in the access token header. Note: In the section quoted above, the RFC states also that the “kid” (or other parameters) maybe used to “include or exlude” certain keys.
Background: The topic came up when comparing the token validation of Open Policy Agent with Spring Boot / Spring Security. The later shows the requested by behavior by default (no configuration option).
Test case (works also in playground)
package jwks
jwks := `{"keys":[{"kid":"right", "kty":"RSA", "use":"sig", "alg":"RS256", "e":"AQAB", "n":"sO7oRm0lkeuE4xEinffrR16XW7f0yAD-zNJV2WmmfqURxflhd9iy21CShCzAJZneRwVOGG3UKcmRn6AWrdR1NJE26Y6A8bgrrg6Ssl4o8QT5cLXNouKHQBTRwec0l_Y3RZIcCC2yJ3mJjZXiwWjFVFqZWM3h2JLK2Fr4z0GR2TKgoTLj2-9EeNyptVKNPxVvFLEE3-2YAA6CETBNmsl-J3hHFNea_LX5xB4Sq65NRYEPiAi0e4cEcMwAgePwa0gZRScSjp851vfkSJPPQe-dkkcOT52TtIPwQPOmnNtcAoUhVH0WKHBhG7Pjxe8ppd4zkg-qLbrWMBVhDsPxSYvWlGPyeExyUcInEwllOtlyKzVw9exX5-UDGoELYWC5kT0V7M17bPJKQk0QKq-n6ag03jI0WtI6I1jZcYICGALV6qOSMkPZTEX9Hh1cFXXSnUjzahtWHV6pjmAG7Qi7u8FD9sv532Y1N_rtVjXGhdkfFZ_sQK_3K7ZesK1kVZ_XMe3Ea_1k1Os2JBt5hoG4xF-CsYqQerxFZ5COezNqXIuMIJ0lXn-WpVqbGZ5ZdyGhhjZUNsxmtVKmkPWmO1zHnwKFbEcV5P2MawhlknV8kmlHBbPKbeT1vAs-bSy-5jGSWFfsDLY4tMHyXkGQsccgeyDKrPb9rG09YTAmlWv_VbuKAJk"}]}`
token = {"valid": valid, "payload": payload} {
[valid, _ , payload] := io.jwt.decode_verify(input.encoded_token, {"cert": jwks})
}
# Test with RS256 signed-JWT with a token header indicating it was signed with a key with "kid" = "right".
# token.valid is expected to be true,
# as the JWT contains "kid" = "right" and JWT's signature can be validated with the configured JWKS using the key with "kid" = "right".
# Note: The JWT was signed with the private key belonging to the public key in the JWKS.
test_right_kid_in_token {
ip := {
#JWT Header: {"alg": "RS256","typ": "JWT","kid": "right"}
"encoded_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJpZ2h0In0.eyJzdWIiOiJZV3hwWTJVPSIsImlzcyI6InRlc3Rpc3N1ZXIiLCJuYmYiOjE1MTQ4NTExMzksImV4cCI6MTc0MTA4MTUzOX0.F4Gx1fnBneQLQEqvCtE_eZT-1vMJAT7Wn1vXmfDdqL6hNYNZXAA-D7qIpasYE0Iaek7xNdICYMljWkm-fOSUNXi2WAw6JwFpwzFBb4Y336pOBiuLDKOW7xaw_52u6HWa0ItY8fpX_aJno1-1Hq95Dt8h-Oq1jsp6oliHuIQxaQZE6C8N1UU8ajID1vb2xPAm3X6NiOpSJH7iWOzX2N7t_7MRfmtZjCUkpK7_DgFZe7u7-yjUEbekA5_bIAMHb9BJYUk3N3IvjOvmmhtrBcMhv_zCeca8Y4BO4K8uSg42WqopDU33fePxbhj_1kD01M7u50Nyu-XqxduL7DWTVmUW98Wef9qC42XjMqI41zHzg1eP3xwxZ3LrLAirwrqjDyDWtU0GmHpbiUoaYDRqJxXIJwnL2RbzP77Kg9-lXd1e7jEKeJ3RY8MPS9LMOaTj8MNqL_kEsoJ3CSNpuPPER0YMiMIXIWIlAeFfYQstqAHtMjj79518xdliwV9AmQQBdKj09J1cXuoKSpBRVntIzD-67yVbTYUOK6hLHKzc8QgbNk54--6XGnYJJ8B0-liEH2wm_BCyRaZBaPmZEI7CfOo01jhdCflxikjmxekPnv4ypPkEnlL1CClCH1S4ojEmJ6rgI7mxXS04gvPzLBDTcBrMSdtQQlIa-vLoqAQq3tK9b0I"
}
token.valid with input as ip
}
# Test with RS256 signed-JWT with a token header indicating it was signed with a key with "kid" = "wrong"
# token.valid is expected to be FALSE (at least in the "STRICT" mode when key-ids are compared),
# as the JWT contains "kid" = "wrong" and JWT's signature can NOT be validated with the configured JWKS using the key with "kid" = "wrong" ,
# as there is no key with "kid"="wrong" in the JWKS.
# Yet, token.valid is TRUE as the token can be validated with a key that is part of the JWKS.
# Note: The JWT was signed with the private key belonging to the public key with kid "right" in the JWKS
# (only the kid claim in the token is not equal to the kid claim of that public key in the JWKS).
test_wrong_kid_in_token {
ip := {
#JWT Header: {"alg": "RS256","typ": "JWT","kid": "wrong"}
"encoded_token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Indyb25nIn0.eyJzdWIiOiJZV3hwWTJVPSIsImlzcyI6InRlc3Rpc3N1ZXIiLCJuYmYiOjE1MTQ4NTExMzksImV4cCI6MTc0MTA4MTUzOX0.jttzvndLzIUXrrKNAs5n4v0AeTWEX77LQBJL5O2-zRkA4ILB2kKOWi43x66zmfqu2Zi0KatazZftlbvIwDr2VaT6UhtrCEZxdVAnX_fnNt2D2dg9hZ6_B27W4ZZE59rmzar0gY2sNFK_VkvYetVkD1-5gB3IM1zQgn3HCIeR3D0O5I-AHm1fedZPuZI8zFCFHb5yo7I-HwOmp2wZEI2Bsi7lRtAGmtck0lhwyd7sIcks42Ma6rUTo0CYfX1GhHiswBWhQoS6LChuUIuBwlczJxiJlZ6eqcbPcmrKoG__UVG9UpAIbmZhqaNixd0YKQdVLmEHprF6dBiuwlTtuImQUPmz8f9DMAl13w18W6dPyebkLbDhM-rxmC8RRPJy8wJhgrXdZLEI6xE3kbHAyw8k5f5ExSALCHhlV5h-W-YSrHeeoIapaIZLGwWnnnbEBdJYd6E23-KQS9XgEkZGBNw5T-Kt5it_GFRX-x4jiq_pbpOgjg_1E5c5U_lKE6CyVYeZes08c0cVcs1IPBaP8I2S80tg8Wb1UC0znFyIaSZ7RcFdniVkVDlZ1Z2L0bHu1bLNOk7mY8_eAqDRo9VvMuhZKluW-ndDReM-9Pdv1kOXhs2PD7s-8O_bpW35aVkLYgTzzoLM6YLFy87-5Jl8IdyzUzrqt1avd3hPQu88iBCml4M"
}
not token.valid with input as ip
}
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 16 (8 by maintainers)
Sounds good! Let’s keep it open for a while and see if others would like this addition. Thanks for an interesting conversation 👍
Yes, for me the solution we came up with above is ok. The additional marshalling might become or might not become a performance topic, but for now, I’m fine with this.