Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The critical timing-oracle risk from exception trace logging must be resolved before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
What changed in this PR
Hardens RSA1_5 JWE decryption by substituting random fallback CEKs after unwrap failures.
Changes:
- Adds and registers an RSA1_5 wrapper across all crypto providers.
- Adds wrapper and JWE regression tests.
| File | Description |
|---|---|
crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/FIPS1402JWETest.java |
Adds FIPS regression coverage. |
crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java |
Registers the wrapper for FIPS. |
crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java |
Registers the wrapper for Elytron. |
crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java |
Registers the wrapper for the default provider. |
core/src/test/java/org/keycloak/jose/JWETest.java |
Adds RSA1_5 and RSA-OAEP regression tests. |
core/src/test/java/org/keycloak/jose/jwe/alg/RSA15WrapperAlgorithmProviderTest.java |
Tests wrapper behavior and encoding delegation. |
core/src/main/java/org/keycloak/jose/jwe/alg/RSA15WrapperAlgorithmProvider.java |
Implements fallback CEK handling. Critical: Exception trace logging adds failure-specific work before authentication, potentially restoring a timing oracle; suppress per-attempt unwrap exception logging or use aggregate metrics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.federation.ldap.LDAPUserLoginTest#loginLDAPUserCredentialVaultAuthenticationNoneEncryptionStartTLSKeycloak CI - Java Distribution IT (windows-latest - temurin - 21) |
|
|
||
| providers.put(CryptoConstants.A128KW, new AesKeyWrapAlgorithmProvider()); | ||
| providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding")); | ||
| providers.put(CryptoConstants.RSA1_5, new RSA15WrapperAlgorithmProvider(new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"))); |
There was a problem hiding this comment.
Is the RSA1_5-only scope deliberate, or should the same wrapper go around RSA_OAEP and RSA_OAEP_256?
RSA15WrapperAlgorithmProvider is registered for CryptoConstants.RSA1_5 in all three crypto providers, and substitutes a random CEK when decodeCek throws or returns a wrong-length one. The two lines below it keep the plain OAEP providers, and testRSAOAEP_CekUnwrapFailureIsNotSubstituted pins that with assertNotEquals(AEADBadTagException.class, cause.getClass()).
I ran all three algs on main against A128GCM, comparing an all-zero encrypted CEK with a one-bit-flipped ciphertext. They behave identically: BadPaddingException("Padding error in decryption") for the bad CEK, AEADBadTagException("mac check in GCM failed") for the bad ciphertext. So the difference this PR removes for v1.5 is still there for OAEP.
There was a problem hiding this comment.
@oleksii-udovychenko Thanks for checking this across all three algorithms. Actually the initial scope and concern of this issue (and its precedent issue/PR [#52205]) was deliberately limited to RSA1_5 to mitigate the Bleichenbacher-style padding oracle attack which applies to RSA1_5 (and its implicit timing based in this PR)
but I think you're right that the OAEP providers still stop before content authentication when CEK decoding fails, and even though they're not vulnerable to that specific Bleichenbacher attack, that doesn't guarantee immunity against others, so I think it makes sense to apply the same hardening to them as well.
I'll address this and update the PR, @mposolda FYI
mposolda
left a comment
There was a problem hiding this comment.
@hagerm98 Thanks for the PR!
What do you think about the inline comment by @oleksii-udovychenko ? I am leaving to your decision whether you prefer to keep PR as it is or whether to add that wrapper against other RSA ciphers. Hopefully same wrapper can be used for all algorithms? If you think it makes sense, please rename class to RSAWrapperAlgorithmProvider .
Closes keycloak#53084 Signed-off-by: Hager Khamis <hagerm98@hotmail.com>
6953ffa to
0312cd9
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
OAEP registrations unintentionally alter unwrap and configuration failure behavior.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 3
Open (3)
Resolved since last review (1)
| providers.put(CryptoConstants.RSA_OAEP, new RSAWrapperAlgorithmProvider(new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"))); | ||
| providers.put(CryptoConstants.RSA_OAEP_256, new RSAWrapperAlgorithmProvider(new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"))); |
| providers.put(CryptoConstants.RSA_OAEP, new RSAWrapperAlgorithmProvider(new ElytronRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"))); | ||
| providers.put(CryptoConstants.RSA_OAEP_256, new RSAWrapperAlgorithmProvider(new ElytronRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"))); |
| providers.put(CryptoConstants.RSA_OAEP, new RSAWrapperAlgorithmProvider(new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP))); | ||
| providers.put(CryptoConstants.RSA_OAEP_256, new RSAWrapperAlgorithmProvider(new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP.withDigest(FipsSHS.Algorithm.SHA256)))); |
| providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding")); | ||
| providers.put(CryptoConstants.RSA_OAEP, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding")); | ||
| providers.put(CryptoConstants.RSA_OAEP_256, new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")); | ||
| providers.put(CryptoConstants.RSA1_5, new RSAWrapperAlgorithmProvider(new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"))); |
Summary
This is a follow-up to #52205, which was narrowed to address the OID4VCI error response. The RSA1_5 CEK-decoding hardening discussed there is not specific to OID4VCI, so this PR implements it at the algorithm-provider level instead of adding RSA1_5-specific handling to
JWE.The new wrapper generates a random fallback CEK before delegating RSA1_5 decoding. It uses the decoded CEK only when it has the expected length; otherwise, processing continues with the fallback CEK through content authentication. Encoding is delegated unchanged, and decoding exceptions are available in trace logs.
The wrapper is registered for RSA1_5 in the default, FIPS, and Elytron crypto providers. Other key-management algorithms are unchanged.
Tests
Closes #53084.