feat: add JWE (JSON Web Encryption) decryption support#209
feat: add JWE (JSON Web Encryption) decryption support#209bvogel wants to merge 4 commits intoomniauth:masterfrom
Conversation
|
This looks good to me. Only minor thing I noticed: how about updating the README the options that were added? Overall, the approach seems solid 👍 |
Excellent point, added a section to the README |
| end | ||
|
|
||
| def jwe?(token) | ||
| options.id_token_encryption_alg.to_s != '' && token.to_s.count('.') + 1 == JWE_SEGMENT_COUNT |
There was a problem hiding this comment.
so it's always the same number of segments ?
There was a problem hiding this comment.
for encrypted? yes. Its always 3 for unencrypted and 5 for encrypted
|
We have this code in production for a customer integrating with It'sMe. |
0f1e4c4 to
c3c7835
Compare
|
|
||
| option :logout_path, '/logout' | ||
| option :id_token_encryption_alg, nil # e.g. 'RSA-OAEP', 'RSA-OAEP-256', 'dir' | ||
| option :id_token_encryption_key, nil # PEM string for RSA algorithms; raw bytes/string for 'dir' |
There was a problem hiding this comment.
Raw bytes are a bit tricky to pass through as strings, especially if this config comes from JSON. Would it better to point this to a file?
There was a problem hiding this comment.
Agreed, raw bytes are a pain, however our practical experience showed that in most cases the shared key is usually "just" a string, so it would be fine, but if the maintainers prefer we could opt for a base64 encoding or a file, but as said - our production experience works fine with this format.
There was a problem hiding this comment.
I recommend there be an option to use either a base64 version or a file. In my installations, we load OmniAuth configs from JSON, and raw bytes are not an option.
| options.id_token_encryption_alg.to_s != '' && token.to_s.count('.') + 1 == JWE_SEGMENT_COUNT | ||
| end | ||
|
|
||
| def decrypt_jwe(jwe_token) |
There was a problem hiding this comment.
Would it be better to use the json-jwt or jose gem here?
There was a problem hiding this comment.
We considered both and actually spent time trying to make just json-jwt work for dir before going with the custom path. In production (integrating with the Belgian identity provider It'sMe), we hit errors that we weren't able to fix. In the end we decided bypassing gems entirely for dir and implementing the AES-CBC/AES-GCM layer directly over OpenSSL — which is what decrypt_dir does now.
For RSA-OAEP, json-jwt works correctly and is already a transitive dependency via openid_connect, so we kept it there. RSA-OAEP-256 also required a custom path because json-jwt doesn't support it either.
As for ruby-jose: it would be the most complete single-gem solution and handles all of these cases well, but it's a non-trivial new dependency to impose on all consumers of this gem just for JWE support. Given that the custom code is narrow, well-tested, and avoids that dependency cost, we felt it was the right trade-off, but we're open to switching if the maintainers feel otherwise.
Adds support for providers that wrap the ID token in a JWE envelope
before returning it (e.g. the Belgian It'sMe identity provider mandates
RSA-OAEP-256 encrypted ID tokens). Previously there was no way to use
such providers with this gem.
New options:
- `id_token_encryption_alg` - the key-wrapping algorithm ('RSA-OAEP',
'RSA-OAEP-256', or 'dir')
- `id_token_encryption_key` - PEM string for RSA algorithms; raw bytes
for 'dir' (direct symmetric key agreement)
`decode_id_token` transparently decrypts the JWE envelope before
passing the inner JWS to the existing verification logic. When
`id_token_encryption_alg` is not set the behaviour is unchanged.
Supported algorithms:
- RSA-OAEP: delegated to the json-jwt gem (already a transitive dep)
- RSA-OAEP-256: custom OpenSSL path - requires OpenSSL >= 3.0
- dir: delegated to the json-jwt gem
Supported content encryption: A128GCM, A256GCM, A128CBC-HS256,
A256CBC-HS512.
All error paths (missing key, bad PEM, decryption failure, unknown enc)
are wrapped in CallbackError to preserve the existing error-handling
contract.
Ref: RFC 7516 (JSON Web Encryption)
78e8d74 to
fc600c2
Compare
Motivation
Some OpenID Connect providers mandate that ID tokens are wrapped in a JWE (JSON Web Encryption) envelope before being returned to the relying party. A notable example is the Belgian It'sMe identity provider, which requires RSA-OAEP-256 encrypted ID tokens per the OIDC spec (RFC 7516).
There is currently no way to use such providers with this gem.
What this adds
Two new strategy options:
id_token_encryption_alg'RSA-OAEP','RSA-OAEP-256', or'dir'id_token_encryption_key'dir'decode_id_tokenis extended to transparently detect and decrypt a JWE envelope before passing the inner JWS to the existing verification logic. Whenid_token_encryption_algis not configured the behavior is completely unchanged.Supported algorithms
RSA-OAEPRSA-OAEP-256dirError handling
All failure paths (missing key, malformed PEM, decryption failure, unsupported
enc, bad base64) are caught and re-raised asCallbackError, preserving the existing error-handling contract incallback_phase.Tests
New test file
test/lib/omniauth/strategies/openid_connect_jwe_test.rbwith 17 tests covering:jwe?detection (with/without alg configured, 3-segment vs 5-segment)decrypt_jwefor each alg: missing-key errors, delegation to json-jwt, round-trip decryptionDecryptionFailed,CipherError,ArgumentErrordecode_id_tokenintegration (decrypt called for JWE, skipped for JWS)All 53 tests pass (36 existing + 17 new).
References