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

Support of encryptet payload in JWT(JWE) idtoken #26317

Closed
dexxamannen opened this issue Jun 23, 2022 · 24 comments · Fixed by #26566
Closed

Support of encryptet payload in JWT(JWE) idtoken #26317

dexxamannen opened this issue Jun 23, 2022 · 24 comments · Fixed by #26566
Assignees
Milestone

Comments

@dexxamannen
Copy link

Description

Hello!
We have some issue when working with OIDC extension and use the "private_key_jwt with the PEM key file" flow.
In our case the idtoken is encryptet (JWE), on the token exchange we get an authexception with a respond of 401 on the client.

I can get it to work by update the sourcecode of the OIDC extension in quarkus and then manually decrypt the idtoken with same key as i use for sign the client assertion.
We use a PEM file for this property (quarkus.oidc.credentials.jwt.key-file)

With this code in OIDC extension I can get it to work. (the try catch i added):

import java.security.PrivateKey;
import java.text.ParseException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jwt.EncryptedJWT;

private AuthorizationCodeTokens getAuthorizationCodeTokens(HttpResponse<Buffer> resp) {
        JsonObject json = getJsonObject(resp);
        final String idToken = json.getString(OidcConstants.ID_TOKEN_VALUE);
        final String accessToken = json.getString(OidcConstants.ACCESS_TOKEN_VALUE);
        final String refreshToken = json.getString(OidcConstants.REFRESH_TOKEN_VALUE);
        try {
            EncryptedJWT encryptedJwt = EncryptedJWT.parse(idToken);
            encryptedJwt.decrypt(new RSADecrypter((PrivateKey) clientJwtKey));
            return new AuthorizationCodeTokens(encryptedJwt.getPayload().toString(), accessToken, refreshToken);
        } catch (ParseException | JOSEException ex) {
            LOG.debugf("Exception when try to decrypt: %s", ex.getMessage());
        }

        return new AuthorizationCodeTokens(idToken, accessToken, refreshToken);
}

Should this work out of the box? maybe I missing some configuration-keys, libs or is JWE is not supported yet in OIDC extension?
Best regards
Daniel

Implementation ideas

No response

@quarkus-bot
Copy link

quarkus-bot bot commented Jun 23, 2022

/cc @sberyozkin

@sberyozkin
Copy link
Member

@dexxamannen Hi, as far as quarkus-oidc is concerned it treats such tokens as opaque tokens - it does not know that a current token is in fact an inner-signed and encrypted JWT. Guessing it could be the one based on a number of dots in the sequence is not reliable either. So, when it sees the opaque token, it will try to introspect it remotely - it should really be an OIDC provider which manages the private decryption keys.
Does your provider support an introspection endpoint ?

If not then indeed a code like yours is good, you can simply register it as a custom TokenStateManager,
Please see https://quarkus.io/guides/security-openid-connect-web-authentication#token-state-manager.

@sberyozkin
Copy link
Member

Sorry, forgot TokenStateManager is not relevant in this context

@sberyozkin
Copy link
Member

sberyozkin commented Jun 23, 2022

Does the provider return such a decryption key in a JWK set from its JWK set endpoint ?

@dexxamannen
Copy link
Author

Hi!
I dont think the provider support introspection because i need to set "quarkus.oidc.token.allow-opaque-token-introspection=false". when I have this property to true I get "Token issued to client is not active".

At the setup with provider I was generating both private and public keys via https://mkjwk.org/, the public key was sent to the provider. the private key is only known by me. it is that key i use to decrypt the idtoken and also signing the client_assertion.

I have following config.
quarkus.oidc.auth-server-url=https:///uas
quarkus.oidc.client-id=my-client-id
quarkus.oidc.credentials.jwt.key-file=./src/test/resources/test.pem
quarkus.oidc.application-type=web-app
quarkus.oidc.token.allow-opaque-token-introspection=true

HTTP Security Configuration

quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

Best regards
Daniel

@sberyozkin
Copy link
Member

@dexxamannen So the provider only encrypts the claims with a public key ? Is it a bit risky if this public key gets exposed somehow not only to the provider ? With the encryption only, one can't assert who has really issued the ID token. I thought the provider was sending a nested ID token signed with provider's private key which was encrypted for the extra safety.

The OIDC spec says at the end of https://openid.net/specs/openid-connect-core-1_0.html#IDToken:

ID Tokens MUST be signed using [JWS](https://openid.net/specs/openid-connect-core-1_0.html#JWS) [[JWS](https://openid.net/specs/openid-connect-core-1_0.html#JWS)] and optionally both signed and then encrypted using JWS [JWS] and [JWE](https://openid.net/specs/openid-connect-core-1_0.html#JWE) [JWE] respectively

So signed is a prerequisite for securing ID tokens.
I think it can be reasonable to support such inner-signed and encrypted ID tokens where the providers have no introspection endpoints, but I'm not sure I'd like to support the encrypted only ID tokens.

Can you clarify please if your provider is sending an inner signed and then encrypted ID token ? If yes, what the key encryption and content encryption algorithms does it use ?

Thanks

@sberyozkin
Copy link
Member

@dexxamannen I think, after looking at the code, the default algorithms are used. And it looks like it is signed-encrypted, as I'm assuming the decrypted content in your code is passed for the verification next.

@KristoferPettersson
Copy link

Hi @sberyozkin I am a colleague to @dexxamannen.
As you stated above we as the client must decrypt the id_token with our private key and then pass it thru verification next.
The id_token is also signed but that is currently no issue once the token is decrypted.
As it is now with the encrypted id_token the Quarkus code cant verify the token and gives a 401 response.

Is it possible to add support to Quarkus to handle encrypted tokens?
The code above given by @dexxamannen works for all our needs but I guess it needs to be configurable to not disturb other OIDC flows.

@sberyozkin
Copy link
Member

@KristoferPettersson Hi, sure, I think it is worth it if the token is signed and encrypted.

@KristoferPettersson
Copy link

Sounds great. Is it possible to estimate when this can be implemented in a future version of Quarkus?

@sberyozkin
Copy link
Member

@KristoferPettersson I'll try to get it in shortly, hopefully will make it to 2.11.0.Final

@KristoferPettersson
Copy link

Wow, great. Looking forward to be able to test this. Thanks in advance.

@sberyozkin
Copy link
Member

sberyozkin commented Jun 28, 2022

@KristoferPettersson Np at all, hope I'll get a chance to look at it soon. Signed and encrypted ID tokens returned directly from the provider are rare but if it is a production related issue then we definitely want to support such cases, and it is still standard OIDC. As I said earlier for now we rely on the provider to introspect the tokens which are considered opaque, and in a case like this one it assumes the provider manages the key pair, and will only return some ID token claims as part of the introspection response.
However in your case the provider is not aware of the decryption key so this is why it returns a token is inactive in the introspection response.

I'll just add a decryption-key-location or similar property and if it is set then the decryption will be attempted.

@sberyozkin sberyozkin self-assigned this Jul 4, 2022
@sberyozkin
Copy link
Member

In progress now...

@sberyozkin
Copy link
Member

@dexxamannen @KristoferPettersson FYI, #26566, please test when you get a chance

@dexxamannen
Copy link
Author

@sberyozkin Perfect! we'll do some tests today! thanks for your quick effort!

@dexxamannen
Copy link
Author

dexxamannen commented Jul 6, 2022

@sberyozkin
Hi!
We got it working now locally 👍
But I did some changes, we using a pem file as default, looks like pem its not supported for "quarkus.oidc.token.decryption-key-location". I tried our private key in jwk/json format instead, then it work, but after removed the attribute "alg": "RS256" from the jwk.
When i have specified "alg": "RS256" i got "Illegal base64 character 7b" at startup.
Best Regards
Daniel

@sberyozkin
Copy link
Member

sberyozkin commented Jul 6, 2022

@dexxamannen Thanks for testing it; I'll add another test for the pem formatted key;

As far as JWK is concerned: just adding some property to JSON would not be a problem so perhaps there was some typo (can you paste a test-only JWK for me to check ?); however, as far as the encryption is concerned, RS256 is an unknown value, it must be RSA-OAEP or other encryption algorithm. So when you have a JWK with no algorithm set then this key is not restricted, but if a key is marked with RS256 then it can't be used for encrypting as RS256 is a signature algorithm.

@sberyozkin
Copy link
Member

@dexxamannen I've added one more test with private PEM key and it works OK, but indeed there was NPE there when trying to read it first as JSON, I got it fixed. PEM key is using a PKCS#8 format.

@dexxamannen
Copy link
Author

Hi!
Okey I understand now 👍
When I generating the JWK at https://mkjwk.org/. I leave the "key use" blank, it can be Signature, Encryption or not specified. In case of "not specified" I get the alg attribute with RSA256.
example of Public and Private Keypair:

{
    "p": "yUGjgXaNUuj9wI0IIbW06sthpMpYOGrpLdDvNiJO6VCeXMmsY0JwzzZ-iqVA9rOQi67lVuE852oSGY6eRSSNc4x-lSVbABfegrLzIVcfxhVd0yudgdy_tGkUgeDX_MA_Wl87fDr5kym4Y5ZnN9wyRqh7HZlqUdPBymMi3ZxgYvs",
    "kty": "RSA",
    "q": "xFAfjv7s3uXugd4HZ60mjhEkGXDLqMqfv5U2AA7HF3CduM_rsmfeoY3cHw5mivzvZGk8dtOUvVYDQOkH8859FaKcLE81p_P9fykYPfYP0EH4RcCcMEFnFhk3NODILBU-zPugf1kA0mxbv22GS8tAP5ZEYct8Oom9ZocFU35w4FM",
    "d": "gVfB2_3vhU6d-fiZYIdTlE5hJbPJaNHBTTRbnP_tVebyofffM3ot1vonS6ONaMTt0hhcKyljiS2cBtox3sBwOd8fXy2mw88Rz8xbf4prC2z7eYvG1uCuHC6ARYlW2lJRiV0p9a_HuCtr0MgwVF5NYDcWT1TYOx3yJcp_xxKKXCeXjAId6ZScWEVY5c4JKfzxuPyaNJ5Vp5enHDmOuwddOo4Ra7NRjcISn_kSFiyo6GfC2BGshaXzh_VctZNRG66DnMpVskeNqgLmClR-_hFIrJEQIxftUsRK5ZQQJ51zN20-XbQAp55tkWi7dAgj-iQu_AI1DlXFFj1mP08SfFWnBQ",
    "e": "AQAB",
    "kid": "_odhqFn2_KaNKYTQjX3tkrAfjae4FGr4EEimu4QEvW0",
    "qi": "KTeLNzxav3Zl-J8gxqpc9M_-CsNsh_VXNRErLXDaCPPFNwRHr1lM-8WdaTlaxZ6v47zYWLykWIBTceOH89f-WOMnDxJ3ActWXzuonu_0FpHhpmltqc6zb3xFeuZEFmFlJbnn3tHLEG50itknnQQEa5Gw_YaaW8p91UcmRG9MH9c",
    "dp": "dS6J1GTBzseokEfNt0sEpz16gifrDBZ75NhloCCDz-fH_YDTpgvWgWBad8HWvI47GIniMR7-hkPFfCoFT38D-YaRYagZf0lmnrUxSXVgI8bnFYCsuiNdX99bOHBBcoJBoQ4YJbJ1BNHi8eFuAiFtCKUq4kYkmLZyfLQSZfSaTqc",
    "alg": "RS256",
    "dq": "bkfUcrAiwNTKN4pS_pr2nbhjXydOQXQSab2YqE-k6DYLZFbpQT-4gWj_zzJ3yHxuvymfHeGeHP7EtSIzpXLKMe03bmzQ55jZPyYGyEgCeiuVHRomo7UaBAAGU14zFRCaRuzULLYDEDJvGAqe9tUnMpFnuMhm8TuPepk_FLhjEKE",
    "n": "mlU-gZTX9sgIgRTpEadz5D-xff5Ar9Lt_XZ_dpz2SmFGh0L_IBjDzO9k_J2P3pn_uaFsFrcVXRHOO6kbA6towVTGCwhPZCqqA9wp6QyhDVNdSlexJmFaKOqOq5zRXbjiIHY3SEG5qrEytPbT7wvtEeBwVsgzyrB-3lZa7EDUmwlnvVlh9T_BfncIrf7ef73N23dGy-RBSeGSVqHgaRxEtdvcJgyTYlW-avdJwOw3zh5FFJGhcXiaP7ePfoFv6STw9dvsodLWGBBztAAeIXj7NP-MNfwD7dzgfTqq8IDM2IipwLFA1SWwKiEBQCw2d1ouXCxW3F40de56Hg_x4au3YQ"
}

I did a try with the latest using our PEM file. it works now, thanks!
Best Regards
/Daniel

@sberyozkin
Copy link
Member

Hi @dexxamannen Thanks, yeah, I guess it defaults to RS256 when the action is unspecified. But I've just tried - if you set the algorithm to RSA-OAEP then it will all be correct

@sberyozkin
Copy link
Member

sberyozkin commented Jul 8, 2022

@dexxamannen, this PR will need more work as I've totally forgotten it won't work in a multi-tenancy case, i.e, we can't have a decryption key shared between different tenants. It is not a real technical issue but will require a bit more work.

Let me also clarify, you've mentioned earlier you are using a private_key_jwt authentication method. Do you use the same private key to sign and decrypt ? @pedroigor reminded it can be the case when reviewing the PR. In that case the decryptionKeyLocation may not be needed
Thanks

@sberyozkin
Copy link
Member

@dexxamannen Please ignore my last comment, some Friday confusion on my part, OidcProvider holding the decryption key is tenant-specific

@sberyozkin
Copy link
Member

@dexxamannen the fallback to the already configured private_jwt_key has been added so if it the same key which you use then you won't need to add decryptionKeyLocation

@quarkus-bot quarkus-bot bot added this to the 2.11 - main milestone Jul 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants