Hi there 👋
| 🌥️ Tech |
| 4 high, 5 medium, 3 low |
This MR introduces ECDSA signature support in the authz-client module with a custom ASN.1 DER encoder/decoder and a lightweight AuthzClientCryptoProvider. It also adds a provider ordering mechanism to CryptoProvider to enable deterministic selection among multiple implementations. While the overall direction is positive, several critical issues were identified: non-deterministic provider ordering due to identical order values across all implementations, a getBouncyCastleProvider() method that returns a non-BouncyCastle provider, dead code in concatenatedRSToASN1DER, and multiple bugs in the ASN.1 decoder (indefinite-length handling, weak bounds check). Additionally, test coverage is incomplete and several consistency concerns exist across provider implementations. The review recommends addressing these before merging.
These might need a close look
- 🔴 crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java (L82)
Non-deterministic provider selection: All three main providers (DefaultCryptoProvider, WildFlyElytronProvider, FIPS1402Provider) return order=200, making the sort ineffective. FIPS provider should have highest order (e.g., 300) to be preferred in FIPS environments.
- 🔴 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java (L68)
getBouncyCastleProvider() returns the default KeyStore provider (typically SunJSSE) instead of a BouncyCastle provider. This breaks expectations of callers like BouncyIntegration.PROVIDER. Either rename or provide a genuine BC provider.
- 🔴 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (L103)
Indefinite-length encoding (0x80) is silently mishandled: readLength() returns -1, which callers do not handle gracefully. Since DER mandates definite-length, throw IOException for 0x80.
- 🔴 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (L136)
Bounds check uses limit (total buffer capacity) instead of limit - position (remaining readable bytes). A crafted TLV with a length value designed to exactly match the buffer limit will pass the check even if the remaining data is insufficient, leading to an out-of-bounds read. Use if (length < 0 || length > available()) throw new IOException("Invalid length").
Worth checking
- 🟡 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (L45)
readSequence() does not verify that all bytes are consumed after reading internal elements. If there are trailing bytes after the expected elements, they are silently ignored. This could mask malformed input.
- 🟡 authz/client/src/test/java/org/keycloak/authorization/client/util/crypto/ECDSAAlgorithmTest.java (L56)
The test verifies the DER→ConcatenatedRS direction but not the round-trip (DER→ConcatenatedRS→DER). Adding assertArrayEquals(sign, asn1Des) after re-encoding would validate round-trip correctness.
- 🟡 authz/client/pom.xml (L23)
CryptoIntegration is imported from keycloak-common, which is only a transitive dependency (included via keycloak-core). If the core module changes its dependency chain, this will break. Add keycloak-common as an explicit direct dependency.
- 🟡 crypto/wildfly/src/main/java/org/keycloak/crypto/wildfly/WildFlyElytronProvider.java (L151)
Uses "AES/CBC/PKCS5Padding" while DefaultCryptoProvider and FIPS1402Provider use "AES/CBC/PKCS7Padding". Although PKCS5 is effectively PKCS7 in Java, this inconsistency is confusing and risks interop issues with non-Java clients expecting the standard PKCS7 name.
Small things (take or leave)
- 🔵 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (L25)
ASN1Decoder has a mutable count field and is not thread-safe. Since it's instantiated per-use this is likely safe in practice, but should be documented or refactored to use local variables.
- 🔵 authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Encoder.java (L37)
Non-standard modifier order: static public instead of public static. Reorder for consistency.
- 🔵 authz/client/src/main/java/org/keycloak/authorization/client/util/AuthzClient.java (L95)
The classloader choice (AuthzClient.class.getClassLoader() vs Thread.currentThread().getContextClassLoader()) is a design decision that should be documented, especially for the crypto provider lookup logic.
FriendlyReviewer detected both golden bugs. The wrong provider bug (#1) was correctly identified and given the same HIGH severity. The dead code in
concatenatedRSToASN1DER(#2) was also found and upgraded from LOW to MEDIUM.The 10 supplementary findings include 3 HIGH severity bugs: non-deterministic provider ordering (all providers claim
order=200, making selection arbitrary), mishandled indefinite-length encoding in the ASN.1 decoder, and a bounds check vulnerability in the same decoder. These are genuine security and correctness bugs in code that the golden set did not cover.The findings on incomplete test coverage, transitive dependency fragility, and padding inconsistency between providers show FriendlyReviewer evaluating the change holistically — not just the diff, but how it integrates with the existing provider ecosystem.