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

Option to try all OIDC JWK keys as fallback #42008

Merged
merged 1 commit into from
Aug 12, 2024

Conversation

c15yi
Copy link
Contributor

@c15yi c15yi commented Jul 19, 2024

Instead of changing more logic to check multiple keys as fallback, which would mean bigger changes and also negatively impact performance, I added the option to specify a specific key as a fallback key in case no key could be determined.

@sberyozkin
Copy link
Member

Thanks for the effort, @c15yi, PR looks neat.
But does it really work beyond the moment OIDC server recycles JWKs keys ?
I feel we have to give it a bit more thinking. If we have 2/3 keys only, the performance impact may or may not be noticeable, you may as well get the first key matching the signing key... In fact I think I recall now that this situation is optionally tested during the OIDC RP conformance testing, it is OK not to support it but they do offer it as an optional test...

@c15yi
Copy link
Contributor Author

c15yi commented Jul 22, 2024

In my case, the kid would stay the same after recycling the keys, but you are correct, that might not always be the case.

Do I understand your suggestion correctly, to try out the keys with JsonWebSignature:verifySignature until a matching one was found in OidcProvider.JsonWebKeyResolver:resolveKey and then return that key, if one could be found?

I would create an option to check all keys, but only if there is no kid or x5t set in the JOSE header. If a key is specified in the JOSE header, then it doesn't make sense to check all keys, if that key isn't present in the jwks, does it?

@c15yi
Copy link
Contributor Author

c15yi commented Jul 22, 2024

@sberyozkin updated the PR 🙂

@c15yi c15yi changed the title Added option for a fallback key id Option to try all keys as fallback Jul 22, 2024
@sberyozkin
Copy link
Member

sberyozkin commented Jul 22, 2024

@c15yi, I guess something like this:

public TokenVerificationResult verifyJwtToken(String token, boolean enforceAudienceVerification, boolean subjectRequired,  String nonce)  throws InvalidJwtException {

        if (oidcConfig.jwks.tryAll) {
               TryAllJsonWebKeyResolver resolver = new TryAllJsonWebKeyResolver(jwks.getAllKeys(), asymmetricKeyResolver);
               for (int i = 0; i < resolver.allKeysSize(); i++) {
                   try {
                      return verifyJwtTokenInternal(customizeJwtToken(token), enforceAudienceVerification, subjectRequired, nonce,  (requiredAlgorithmConstraints != null ? requiredAlgorithmConstraints : ASYMMETRIC_ALGORITHM_CONSTRAINTS), resolver, true, oidcConfig.token.isIssuedAtRequired());
                   } catch (InvalidJsonJsonWebKeyException ex) {
                        if (signatureFailure(ex) && resolver.keyIndex() != -1) {
                             continue;
                        }
                        throw ex;
                   }
              }
       } else {
            return verifyJwtTokenInternal(customizeJwtToken(token), enforceAudienceVerification, subjectRequired, nonce,
                    (requiredAlgorithmConstraints != null ? requiredAlgorithmConstraints : ASYMMETRIC_ALGORITHM_CONSTRAINTS), asymmetricKeyResolver, true, oidcConfig.token.isIssuedAtRequired());
       }
    }

where TryAllJsonWebKeyResolver provides an index based access to the algorithm specific list of keys but only if the JsonWebKeyResolver which it delegates to produced null key...JsonWebKeyResolver itself remains unchanged

Please think about it

@c15yi
Copy link
Contributor Author

c15yi commented Jul 23, 2024

@sberyozkin, checking the signature twice is not optimal. What is the expected runtime duration for such check? I am just trying to understand the trade-off here.

I have a few questions regarding your solution proposal.

  • Is it worth (in terms of runtime) to cycle through verifyJwtTokenInternal multiple times until a valid key was found?
  • The TryAllJsonWebKeyResolver would need to hold a state.
    • Every time resolveKey is called on it would return the next key in its list. Feels somehow not so clean.

Edit: When getting the keys from the jwks we would get all keys (for all algorithms) right? Or did you have some implementation in mind to select the correct algorithm based on the string jwt?

@sberyozkin
Copy link
Member

sberyozkin commented Jul 23, 2024

@c15yi This is only a very rough prototype

Is it worth (in terms of runtime) to cycle through verifyJwtTokenInternal multiple times until a valid key was found?

As far as I understand, it could be the most effective, albeit the messy one. In this case, given 3 keys, you'll get at most 3 verifications in the worst case but only 1 in the best case. While with your last update, with 3 keys, in the worst case, you'll get 4 and in the best case 2 verifications. I've typed it and I think it is indeed not a big deal, so let's scrap my proposal, thanks for raising concerns about it.

But it really should be a Map of algorithms to the list of keys. Each JWK must have an alg property so you can use it to prepare this map, and then you can get an algorithm from a token header to acquire a matching List of keys to check with the brute force technique... JWKs without the alg property can not be really accepted for this case

Thanks

@c15yi c15yi requested a review from sberyozkin July 24, 2024 14:56
@sberyozkin sberyozkin changed the title Option to try all keys as fallback Option to try all OIDC JWK keys as fallback Jul 24, 2024
@sberyozkin
Copy link
Member

Thanks for your work on this PR, @c15yi, it really looks good already, but I've asked for a few minor updates, thanks

@sberyozkin
Copy link
Member

@c15yi FYI, I'll be going on PTO later today so we'll continue in mid of August, talk to you then

@c15yi c15yi requested a review from sberyozkin July 25, 2024 10:34
@c15yi
Copy link
Contributor Author

c15yi commented Jul 25, 2024

@sberyozkin, in case you are still available, I just implemented your suggestions.

Otherwise, have a great PTO, thanks for the reviews and talk to you then.

@c15yi
Copy link
Contributor Author

c15yi commented Jul 25, 2024

Because our application is depending on this, would it be possible for someone else to take over the PR review, @sberyozkin? In the issue I created @pedroigor was mentioned for oidc, too. What do you think?

// if (newKey == null && tryAll && kid == null && thumbprint == null) {
// LOG.debug("JWK is not available, neither 'kid' nor 'x5t#S256' nor 'x5t' token headers are set,"
// + " falling back to trying all available keys");
// newKey = jwks.findKeyInAllKeys(jws); // there is nothing to check the signature for in this method
Copy link
Member

@sberyozkin sberyozkin Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify this comment before uncommenting ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code of the DynamicVerificationKeyResolver there is no JsonWebSignature, but it need such object to perform the brute force algorithm on it with the different keys.
Therefore, I commented out the code, because it's not functional and I didn't see an easy solution to getting this to work there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@c15yi ok, a jose4j instance for verifying a signature can be built manually for this case with some custom basic resolver delegating to your new method, but I can take care of it later

@sberyozkin
Copy link
Member

@c15yi Sure, @pedroigor can definitely review.

Your PR is nearly ready to go, couple of questions only there, and please squash it, keeping the main commit message only.

Note though, I think it will only make it into 3.14, I'll be back by then:
https://github.com/quarkusio/quarkus/wiki/Release-Planning#314-proposal

But I may be able to merge approve later today as well, once the last minor details are sorted out

@sberyozkin sberyozkin self-requested a review July 25, 2024 13:50
Copy link
Member

@sberyozkin sberyozkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's ready to go, dynamic resolver update can wait, no need to rush

This comment has been minimized.

@c15yi c15yi force-pushed the feature/oidc-default-key branch 2 times, most recently from bbdfb06 to 66b1f0c Compare July 25, 2024 14:09
@c15yi
Copy link
Contributor Author

c15yi commented Jul 25, 2024

@sberyozkin squashed the commits and added an explanation to your question on the DynamicVerificationKeyResolver. :)

@sberyozkin sberyozkin added the triage/waiting-for-ci Ready to merge when CI successfully finishes label Jul 25, 2024

This comment has been minimized.

Copy link

quarkus-bot bot commented Jul 26, 2024

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit 41de0f5.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.

@geoand
Copy link
Contributor

geoand commented Aug 1, 2024

@sberyozkin should this be merged?

@c15yi
Copy link
Contributor Author

c15yi commented Aug 7, 2024

@geoand, @sberyozkin is currently on vacation and will be back mid of August. He approved it, but the pipelines failed, because I missed an unused import. Now everything should be ready I guess.

@geoand
Copy link
Contributor

geoand commented Aug 7, 2024

Thanks.

Let's wait for @sberyozkin to re-check

@geoand geoand removed the triage/waiting-for-ci Ready to merge when CI successfully finishes label Aug 7, 2024
@sberyozkin
Copy link
Member

Thanks @c15yi, @geoand, sorry but did not really have good options for merging it since heading to the airport on 27th July, but we still have a few days to have it merged and backported :-)
I'll keep the commented out code in place for now, it is on a non-critical path as far as this issue is concerned and I'll clean it up a bit later

@sberyozkin sberyozkin merged commit d338203 into quarkusio:main Aug 12, 2024
23 checks passed
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label Aug 12, 2024
@quarkus-bot quarkus-bot bot added this to the 3.14 - main milestone Aug 12, 2024
@sberyozkin
Copy link
Member

No need to backport, it is just in time for 3.14 CR1

final CertChainPublicKeyResolver chainResolverFallback;

public DynamicVerificationKeyResolver(OidcProviderClient client, OidcTenantConfig config) {
this.client = client;
this.tryAll = config.jwks.tryAll;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this variable is never used

@@ -115,6 +117,12 @@ public Uni<? extends VerificationKeyResolver> apply(JsonWebKeySet jwks) {
newKey = jwks.getKeyWithoutKeyIdAndThumbprint("RSA");
}

// if (newKey == null && tryAll && kid == null && thumbprint == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not useful to have this commented out code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/oidc kind/enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OIDC: List of JWKS is not checked without specified kid
4 participants