Skip to content

Add AuthzClientCryptoProvider for authorization client cryptographic operations#8

Open
akshayutture-augment wants to merge 1 commit into
feature-authz-crypto-baselinefrom
feature-authz-crypto-implementation
Open

Add AuthzClientCryptoProvider for authorization client cryptographic operations#8
akshayutture-augment wants to merge 1 commit into
feature-authz-crypto-baselinefrom
feature-authz-crypto-implementation

Conversation

@akshayutture-augment

@akshayutture-augment akshayutture-augment commented Nov 14, 2025

Copy link
Copy Markdown

Test 3

Summary by CodeRabbit

  • New Features

    • Added ECDSA cryptographic support to the authorization client.
    • Implemented priority-based selection when multiple cryptography providers are available.
  • Tests

    • Added test suite for ECDSA algorithm verification across ES256, ES384, and ES512.
  • Chores

    • Added test framework dependencies.
    • Registered new cryptography provider implementation for service discovery.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#3

…tory

closes #33831

Signed-off-by: mposolda <mposolda@gmail.com>
@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 22, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Nov 22, 2025

Copy link
Copy Markdown

Walkthrough

This PR adds ECDSA cryptography support to the authorization client by introducing ASN.1 encoding/decoding utilities, a new AuthzClientCryptoProvider implementation, and provider ordering capability. The CryptoProvider interface gains an order() method, implemented across existing and new providers, enabling prioritized provider selection when multiple implementations are available.

Changes

Cohort / File(s) Summary
ASN.1 Utilities
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java, authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Encoder.java
New encoder and decoder classes for DER-encoded ASN.1 structures. ASN1Decoder parses SEQUENCE and INTEGER values with robust error handling for EOF, invalid tags, and malformed lengths. ASN1Encoder builds DER SEQUENCE and INTEGER structures using ByteArrayOutputStream.
AuthzClient Crypto Provider
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
New CryptoProvider implementation providing ECDSA support. Converts between DER-encoded signatures and concatenated RS format for ES256/ES384/ES512. Implements order() returning 100. Stubs remaining CryptoProvider methods.
CryptoProvider Interface & Implementations
common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java, crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java, crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java, crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java
Added order() method to CryptoProvider interface. Implemented in existing providers (DefaultCryptoProvider, WildFlyElytronProvider, FIPS1402Provider) all returning 100, establishing selection precedence.
CryptoIntegration Provider Detection
common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java
Updated detectProvider() to sort providers by order() in descending order. Removed exception for multiple providers; instead logs the highest-priority provider and additional ones as "Ignored crypto providers".
AuthzClient Initialization
authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java
AuthzClient.create() now calls CryptoIntegration.init() during initialization.
Service Loader Registration
authz/client/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider
Registers AuthzClientCryptoProvider for runtime discovery via Java ServiceLoader.
Test Infrastructure
authz/client/pom.xml, authz/client/src/test/java/org/keycloak/authorization/client/test/ECDSAAlgorithmTest.java
Added junit and hamcrest test dependencies. New ECDSAAlgorithmTest verifies DER↔concatenated RS round-trip conversions for ES256, ES384, ES512.

Sequence Diagram

sequenceDiagram
    autonumber
    participant Client as AuthzClient
    participant CryptoInt as CryptoIntegration
    participant SM as ServiceLoader
    participant DefProv as DefaultCryptoProvider
    participant AuthzProv as AuthzClientCryptoProvider
    
    Client->>CryptoInt: init()
    CryptoInt->>SM: load all CryptoProvider impls
    SM-->>CryptoInt: [AuthzClientCryptoProvider(order=100),<br/>DefaultCryptoProvider(order=200), ...]
    
    rect rgb(200, 220, 240)
    Note over CryptoInt: Sort by order() descending
    CryptoInt->>CryptoInt: sort(providers, order DESC)
    end
    
    CryptoInt->>AuthzProv: getEcdsaCryptoProvider()
    AuthzProv-->>CryptoInt: ECDSACryptoProvider instance
    
    Note over CryptoInt: Log: "Detected: AuthzClientCryptoProvider"<br/>"Ignored: DefaultCryptoProvider, ..."
    CryptoInt-->>Client: CryptoIntegration ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • ASN1Decoder.java: Complex stream parsing with error handling for EOF, invalid tags, long-form length encoding, and mark/reset semantics—requires careful verification of boundary conditions and error cases
  • AuthzClientCryptoProvider.java: ECDSA signature conversion logic (concatenatedRS↔DER) using BigInteger arithmetic and ASN.1 structures—cryptographic correctness is critical
  • CryptoIntegration.java: Behavioral change removing exception for multiple providers; verify logging and provider ordering logic
  • Repetitive order() implementations: Four files with similar one-line method additions (DefaultCryptoProvider, WildFlyElytronProvider, FIPS1402Provider, AuthzClientCryptoProvider)

Poem

🐰 With ASN.1 streams now flowing,
Signatures dance in DER encoding,
ECDSA keys take their place,
Providers order'd by grace—
Crypto's future, brightly glowing! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is largely incomplete and vague. It contains only 'Test 3' and a replication note, with no meaningful explanation of the changes, motivation, or implementation details. Provide a comprehensive description following the repository guidelines, including: what problem this solves, implementation approach, and testing validation for the cryptographic provider changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the main change: adding AuthzClientCryptoProvider for authorization client cryptographic operations, which aligns with the primary objective evident in the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-authz-crypto-implementation

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java (1)

6-6: Provider detection via ordered ServiceLoader is reasonable; consider clarifying tie cases

Sorting discovered CryptoProviders by order() (descending) and selecting the first one is a clean way to express precedence, and the debug log of ignored providers is useful when more than one is present.

Two small points to consider:

  • All built‑in providers currently return the same order() (200), so in mixed environments the effective choice still depends on ServiceLoader iteration order. If you intend a deterministic preference (e.g., FIPS > Elytron > Default), you might want to give them distinct order values.
  • When multiple providers are present, logging their order alongside class names (and maybe flagging equal‑priority ties) would make misconfiguration easier to spot when reading logs.

These are behavioral/diagnostic refinements; the current logic is otherwise sound.

Also applies to: 56-58, 64-70

authz/client/src/test/java/org/keycloak/authorization/client/test/ECDSAAlgorithmTest.java (1)

41-72: ECDSA round‑trip tests look good; minor naming / assertion tweaks optional

The round‑trip coverage for ES256/384/512 via asn1derToConcatenatedRS and concatenatedRSToASN1DER is solid and exercises the new provider correctly. Two small polish ideas:

  • Rename asn1Des (Line 54) to asn1Der for clarity.
  • Optionally assert rsConcat.length == algorithm.getSignatureLength() to catch mismatched signLength/signature sizes early.
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (1)

127-167: Indefinite‑length (0x80) handling is incomplete; better to fail fast

readLength() returns -1 for 0x80 (indefinite length), but readSequence() / readInteger() assume a non‑negative length and don’t special‑case -1. For DER, indefinite lengths are invalid anyway, so silently returning -1 risks odd behavior if this decoder is ever reused beyond ECDSA signatures.

Consider rejecting indefinite lengths explicitly, e.g.:

-        if (length == 0x80) {
-            return -1;      // indefinite-length encoding
-        }
+        if (length == 0x80) {
+            throw new IOException("Indefinite-length encoding not supported");
+        }
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java (1)

100-164: Harden ECDSA DER↔R||S helpers with length checks and avoid redundant encoder instances

The ECDSA conversion logic is essentially correct, but a couple of improvements would make it more robust and cleaner:

  • concatenatedRSToASN1DER assumes signature.length == signLength and will throw ArrayIndexOutOfBoundsException if misused. It’s safer to validate upfront and fail with a clear message.
  • Lines 114–115 create ASN1Encoder instances whose results are discarded; writeDerSeq then creates yet more encoders. You can build the r/s encoders once and reuse them.
  • integerToBytes silently truncates when bytes.length > qLength. For stronger validation, you could check for unexpected oversize values (e.g., more than qLength + 1 bytes or a non‑zero leading byte) and throw an IOException instead of dropping high‑order bits.

Example for the first two points:

             @Override
             public byte[] concatenatedRSToASN1DER(byte[] signature, int signLength) throws IOException {
                 int len = signLength / 2;
+                if (signature.length != signLength) {
+                    throw new IOException("Expected signature of length " + signLength +
+                            " but got " + signature.length);
+                }
                 int arraySize = len + 1;

                 byte[] r = new byte[arraySize];
                 byte[] s = new byte[arraySize];
                 System.arraycopy(signature, 0, r, 1, len);
                 System.arraycopy(signature, len, s, 1, len);
                 BigInteger rBigInteger = new BigInteger(r);
                 BigInteger sBigInteger = new BigInteger(s);

-                ASN1Encoder.create().write(rBigInteger);
-                ASN1Encoder.create().write(sBigInteger);
-
-                return ASN1Encoder.create()
-                        .writeDerSeq(
-                                ASN1Encoder.create().write(rBigInteger),
-                                ASN1Encoder.create().write(sBigInteger))
-                        .toByteArray();
+                ASN1Encoder rEnc = ASN1Encoder.create().write(rBigInteger);
+                ASN1Encoder sEnc = ASN1Encoder.create().write(sBigInteger);
+                return ASN1Encoder.create()
+                        .writeDerSeq(rEnc, sEnc)
+                        .toByteArray();
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10aca55 and b95d12a.

📒 Files selected for processing (12)
  • authz/client/pom.xml (1 hunks)
  • authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java (2 hunks)
  • authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java (1 hunks)
  • authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Encoder.java (1 hunks)
  • authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java (1 hunks)
  • authz/client/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider (1 hunks)
  • authz/client/src/test/java/org/keycloak/authorization/client/test/ECDSAAlgorithmTest.java (1 hunks)
  • common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java (2 hunks)
  • common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java (2 hunks)
  • crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java (2 hunks)
  • crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java (1 hunks)
  • crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java (3 hunks)
🔇 Additional comments (14)
authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java (1)

33-33: Wire-up of CryptoIntegration in AuthzClient factory looks appropriate

Importing CryptoIntegration and calling CryptoIntegration.init(AuthzClient.class.getClassLoader()) in create(Configuration) is a sensible place to bootstrap the crypto SPI: it keeps initialization lazy, idempotent, and uses the module’s classloader so the AuthzClientCryptoProvider service entry is visible. Just be aware this makes AuthzClient.create(Configuration) dependent on a CryptoProvider being present on the classpath, which is fine for this tests-only module.

Also applies to: 95-96

authz/client/pom.xml (1)

61-71: Test dependencies are scoped and aligned with usage

Adding junit and hamcrest with test scope is appropriate for the new test suite and keeps runtime dependencies unchanged. Relying on the parent/BOM for versions is consistent with the rest of the Keycloak build.

If you haven't already, you may want to confirm via mvn dependency:tree -Dscope=test that there are no conflicting JUnit/Hamcrest versions pulled in transitively.

crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java (1)

75-78: order() implementation satisfies new SPI contract

Providing order() with value 200 correctly implements the new CryptoProvider contract and keeps WildFlyElytronProvider aligned with the other core providers in the same priority tier.

crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java (2)

82-85: DefaultCryptoProvider correctly participates in provider ordering

The new order() implementation returning 200 cleanly satisfies the updated CryptoProvider interface and keeps DefaultCryptoProvider in the same priority class as the other main providers.


186-186: Whitespace-only change

This line change appears to be formatting-only around getSignature and has no behavioral impact.

common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java (1)

95-95: AES‑GCM method in interface matches provider implementations

The getAesGcmCipher declaration, including its checked exceptions, is consistent with the implementations in the default and FIPS providers and doesn’t introduce new concerns.

crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java (5)

113-116: FIPS1402Provider correctly implements provider ordering

The order() implementation returning 200 brings FIPS1402Provider into compliance with the updated CryptoProvider SPI and keeps it in the same priority band as the other core providers.


199-203: Whitespace-only adjustment around AES‑GCM cipher

This hunk appears to be formatting-only around getAesGcmCipher and does not affect behavior.


204-208: Whitespace-only adjustment around SecretKeyFactory

Change here is purely structural/whitespace around getSecretKeyFact and has no runtime effect.


226-230: Whitespace-only adjustment around CertPathBuilder

Non-functional change around getCertPathBuilder; behavior remains the same.


231-235: Whitespace-only adjustment around getSignature

Again, only formatting around getSignature; no semantic changes.

authz/client/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider (1)

1-20: Service descriptor correctly exposes AuthzClientCryptoProvider

The META‑INF/services entry is properly formatted (comments via #, then the FQCN) and will allow ServiceLoader to discover AuthzClientCryptoProvider when CryptoIntegration is initialized with the authz client’s classloader.

authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Encoder.java (1)

30-100: ASN.1 DER encoder implementation looks correct

Tag constants, length encoding, and sequence construction are consistent with DER and with how the decoder and ECDSA provider use this class. No issues spotted.

authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java (1)

69-225: Correct the provider ordering logic and clarify when AuthzClientCryptoProvider is actually selected

The review comment misstates how provider ordering works. In CryptoIntegration.detectProvider(), providers are sorted by .comparingInt(CryptoProvider::order).reversed()—meaning higher order() values take priority. Since AuthzClientCryptoProvider returns 100 while DefaultCryptoProvider, FIPS1402Provider, and WildFlyElytronProvider each return 200, the latter three will win when multiple providers are on the classpath.

However, the underlying risk is real for standalone authz-client usage: when this library is used in isolation (without the full Keycloak server), AuthzClientCryptoProvider may be the only registered provider and will be selected. Any code then calling unsupported methods like getKeyPairGen(), getKeyFactory(), getSignature(), etc., will fail with UnsupportedOperationException.

The registered META-INF/services/org.keycloak.common.crypto.CryptoProvider entry confirms the provider is discoverable. To mitigate:

  • Raise order() above 200, or
  • Implement the commonly-used methods (key factories, signatures, ciphers), or
  • Add guards to prevent selection when insufficient functionality is available.

Likely an incorrect or invalid review comment.

Comment on lines +39 to +45
/**
* Order of this provider. This allows to specify which CryptoProvider will have preference in case that more of them are on the classpath.
*
* The higher number has preference over the lower number
*/
int order();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

New order() method breaks SPI binary compatibility for existing providers

Defining int order(); as a new abstract method on CryptoProvider means any third-party implementations compiled against the previous version will not implement it and will hit AbstractMethodError once CryptoIntegration invokes order() during provider detection.

To preserve SPI compatibility while still enabling ordering, consider making this a default method:

default int order() {
    // Baseline for older providers; newer ones can override with >0
    return 0;
}

This keeps existing implementations working, while letting built‑in providers (Default, Elytron, FIPS, Authz client) override with their desired priority.

🤖 Prompt for AI Agents
In common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java around
lines 39 to 45, the new abstract int order() method breaks SPI binary
compatibility for existing third‑party providers; change it to a default method
that returns a baseline value (e.g. 0) so older implementations remain
compatible while allowing newer providers to override with higher priorities,
and update the Javadoc to document that providers may override order() to
influence preference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants