Skip to content

Add AuthzClientCryptoProvider for authorization client cryptographic operations#3

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

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

Conversation

@everettbu

@everettbu everettbu commented Jul 26, 2025

Copy link
Copy Markdown

Test 3

Summary by CodeRabbit

  • New Features

    • Introduced ASN.1 encoder and decoder utilities for cryptographic operations.
    • Added a new cryptographic provider for the authorization client with support for ECDSA signature format conversions.
    • The system now supports prioritization of cryptographic providers using a new ordering mechanism.
  • Bug Fixes

    • Improved handling when multiple cryptographic providers are present, selecting the highest-priority provider instead of throwing an error.
  • Tests

    • Added tests for ECDSA signature algorithms to verify correct signature format conversions.
  • Chores

    • Added test dependencies for improved testing capabilities.

…tory

closes #33831

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

coderabbitai Bot commented Jul 26, 2025

Copy link
Copy Markdown

Walkthrough

This change introduces a new crypto provider for the authorization client, including ASN.1 encoding/decoding utilities and ECDSA signature format conversion. It updates the crypto provider discovery logic to prioritize providers using a new order() method, which is added to the CryptoProvider interface and implemented by all relevant providers. Associated tests and dependencies are also added.

Changes

Cohort / File(s) Change Summary
Authorization Client Crypto 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, authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
Introduced ASN.1 DER encoder/decoder classes and a new crypto provider implementation for the authorization client, supporting ECDSA signature format conversion and basic keystore retrieval.
Authorization Client Integration
authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java
Added initialization of cryptographic integration in the static factory method.
Crypto Provider SPI and 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 the CryptoProvider interface and implemented it in all existing provider classes with a fixed priority value.
Crypto Provider Discovery Logic
common/src/main/java/org/keycloak/common/crypto/CryptoIntegration.java
Changed provider detection to sort by order(), select the highest, and log ignored providers, replacing the previous error on multiple providers.
Service Provider Registration
authz/client/src/main/resources/META-INF/services/org.keycloak.common.crypto.CryptoProvider
Registered the new AuthzClientCryptoProvider as a service provider for CryptoProvider.
Testing and Dependencies
authz/client/pom.xml, authz/client/src/test/java/org/keycloak/authorization/client/test/ECDSAAlgorithmTest.java
Added test dependencies (junit, org.hamcrest) and a new test class verifying ECDSA signature conversion correctness for all supported algorithms.

Sequence Diagram(s)

sequenceDiagram
    participant Client as AuthzClient.create()
    participant Crypto as CryptoIntegration
    participant Provider as AuthzClientCryptoProvider

    Client->>Crypto: init()
    Crypto->>Crypto: detectProvider(classLoader)
    Crypto->>Provider: (if selected) initialize provider
    Crypto-->>Client: (provider ready)
    Client-->>Client: return new AuthzClient instance
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

In the warren of code where the rabbits encrypt,
New crypto hops in, with ASN.1 script.
ECDSA signatures, now easy to swap,
Providers line up, each hoping to top.
With order and logic, the best one is picked—
Secure little carrots, all properly licked!
🥕🔐

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-authz-crypto-implementation

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai

coderabbitai Bot commented Jul 29, 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.

@everettbu

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 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 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: 6

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

64-70: Minor improvement: Remove trailing comma in debug message.

The current implementation will produce a trailing comma and space in the debug message.

-            if (foundProviders.size() > 1) {
-                StringBuilder builder = new StringBuilder("Ignored crypto providers: ");
-                for (int i = 1 ; i < foundProviders.size() ; i++) {
-                    builder.append(foundProviders.get(i).getClass().getName() + ", ");
-                }
-                logger.debugf(builder.toString());
-            }
+            if (foundProviders.size() > 1) {
+                String ignoredProviders = foundProviders.stream()
+                    .skip(1)
+                    .map(p -> p.getClass().getName())
+                    .collect(Collectors.joining(", "));
+                logger.debugf("Ignored crypto providers: %s", ignoredProviders);
+            }
authz/client/src/test/java/org/keycloak/authorization/client/test/ECDSAAlgorithmTest.java (2)

41-43: Consider specifying EC curve parameters explicitly.

The current implementation uses default EC parameters. For more predictable behavior across different JVM implementations, consider specifying the curve explicitly.

     public ECDSAAlgorithmTest() throws Exception {
-        keyPair = KeyPairGenerator.getInstance("EC").genKeyPair();
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+        keyGen.initialize(new java.security.spec.ECGenParameterSpec("secp256r1"));
+        keyPair = keyGen.generateKeyPair();
     }

46-57: Consider adding signature verification to the test.

While the test verifies format conversion correctness, it doesn't verify that the converted signatures remain valid.

     private void test(ECDSAAlgorithm algorithm) throws Exception {
         AuthzClientCryptoProvider prov = new AuthzClientCryptoProvider();
         byte[] data = "Something to sign".getBytes(StandardCharsets.UTF_8);
         Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(algorithm.name()));
         signature.initSign(keyPair.getPrivate());
         signature.update(data);
         byte[] sign = signature.sign();
         byte[] rsConcat = prov.getEcdsaCryptoProvider().asn1derToConcatenatedRS(sign, algorithm.getSignatureLength());
         byte[] asn1Des = prov.getEcdsaCryptoProvider().concatenatedRSToASN1DER(rsConcat, algorithm.getSignatureLength());
         byte[] rsConcat2 = prov.getEcdsaCryptoProvider().asn1derToConcatenatedRS(asn1Des, algorithm.getSignatureLength());
         Assert.assertArrayEquals(rsConcat, rsConcat2);
+        
+        // Verify the converted signature is still valid
+        signature.initVerify(keyPair.getPublic());
+        signature.update(data);
+        Assert.assertTrue("Converted signature should be valid", signature.verify(asn1Des));
     }
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Encoder.java (2)

66-83: DER length encoding implementation is correct but could be optimized.

The implementation correctly handles both short form (≤127) and long form (>127) length encoding according to DER rules. However, there's potential for optimization in the bit shifting logic.

Consider this more readable implementation:

 void writeLength(int length) throws IOException {
     if (length > 127) {
-        int size = 1;
-        int val = length;
-
-        while ((val >>>= 8) != 0) {
-            size++;
-        }
+        int size = (Integer.SIZE - Integer.numberOfLeadingZeros(length) + 7) / 8;
 
         write((byte) (size | 0x80));
 
         for (int i = (size - 1) * 8; i >= 0; i -= 8) {
             write((byte) (length >> i));
         }
     } else {
         write((byte) length);
     }
 }

93-99: Object concatenation method is inefficient for large sequences.

The method creates a new ByteArrayOutputStream and copies data from multiple encoders, which could be memory-intensive for large sequences.

Consider pre-calculating total size to avoid internal array resizing:

 byte[] concatenate(ASN1Encoder... objects) throws IOException {
+    int totalSize = 0;
+    for (ASN1Encoder object : objects) {
+        totalSize += object.toByteArray().length;
+    }
-    ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+    ByteArrayOutputStream tmp = new ByteArrayOutputStream(totalSize);
     for (ASN1Encoder object : objects) {
         tmp.write(object.toByteArray());
     }
     return tmp.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)
common/src/main/java/org/keycloak/common/crypto/CryptoProvider.java (1)

39-44: Verified CryptoProvider interface implementations updated

All direct implementers of CryptoProvider now include the new order() method, so the interface change is fully addressed:

  • crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java
  • crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java
  • crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java
  • authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java (1)

95-95: LGTM - Proper crypto integration initialization.

The addition of CryptoIntegration.init() ensures that the new AuthzClientCryptoProvider and related cryptographic utilities are properly loaded when creating AuthzClient instances. Using the AuthzClient class loader is appropriate for this context.

authz/client/pom.xml (1)

62-71: LGTM - Standard test dependencies added.

The addition of JUnit and Hamcrest dependencies with test scope is appropriate for supporting the new ECDSA algorithm tests. These are standard, well-maintained testing libraries.

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

75-78: LGTM - Consistent priority implementation.

The order() method implementation returning 200 is consistent with other crypto providers and gives this provider appropriate high priority in the selection mechanism.

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

113-116: LGTM - Consistent FIPS provider priority implementation.

The order() method implementation returning 200 maintains consistency with other crypto providers and ensures the FIPS provider has appropriate high priority in the selection mechanism.

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

82-85: LGTM!

The implementation of the order() method is correct and aligns with the new priority mechanism for crypto providers.

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

1-21: Service provider registration looks good.

The file correctly registers AuthzClientCryptoProvider as a CryptoProvider implementation following the Java SPI pattern.

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

32-34: ASN.1 tag constants are correct.

The ASN.1 tag values are properly defined according to ITU-T X.690 standard: INTEGER (0x02), SEQUENCE (0x10), and CONSTRUCTED (0x20).


38-44: Factory method pattern implementation is sound.

The private constructor with public factory method provides good encapsulation and clear instantiation semantics.


46-49: BigInteger encoding correctly delegates to writeEncoded.

The method properly encodes BigInteger values as ASN.1 INTEGER using toByteArray() which handles two's complement representation correctly.


51-54: SEQUENCE encoding implementation is correct.

The method properly constructs ASN.1 SEQUENCE with CONSTRUCTED bit set and concatenates nested objects correctly.

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

125-144: ASN.1 DER to concatenated RS conversion looks correct.

The method properly parses the ASN.1 sequence, extracts the two BigInteger values, and converts them to fixed-length byte arrays. The error handling for invalid sequence size is appropriate.


198-200: KeyStore implementation is correct.

The method properly delegates to the standard KeyStore.getInstance() with the format name, which is the expected behavior for this minimal crypto provider.


104-112: Verify ECDSA DER Conversion Against Test Vectors
I wasn’t able to find existing unit tests invoking concatenatedRSToASN1DER to validate how the leading-zero byte and BigInteger sign handling behave across different R/S MSB values. Please confirm or add tests using known ECDSA signature vectors (covering both MSB=0 and MSB=1 cases) to ensure correct DER encoding.

• File: authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
Lines: 104–112

Comment on lines +49 to +63
public List<byte[]> readSequence() throws IOException {
int tag = readTag();
int tagNo = readTagNumber(tag);
if (tagNo != ASN1Encoder.SEQUENCE) {
throw new IOException("Invalid Sequence tag " + tagNo);
}
int length = readLength();
List<byte[]> result = new ArrayList<>();
while (length > 0) {
byte[] bytes = readNext();
result.add(bytes);
length = length - bytes.length;
}
return result;
}

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

Validate remaining length in readSequence to prevent negative values.

The method subtracts bytes.length from length without checking if it would go negative, which could happen with corrupted data.

     public List<byte[]> readSequence() throws IOException {
         int tag = readTag();
         int tagNo = readTagNumber(tag);
         if (tagNo != ASN1Encoder.SEQUENCE) {
             throw new IOException("Invalid Sequence tag " + tagNo);
         }
         int length = readLength();
         List<byte[]> result = new ArrayList<>();
         while (length > 0) {
             byte[] bytes = readNext();
+            if (bytes.length > length) {
+                throw new IOException("Sequence element length exceeds remaining sequence length");
+            }
             result.add(bytes);
             length = length - bytes.length;
         }
+        if (length != 0) {
+            throw new IOException("Sequence length mismatch: " + length + " bytes remaining");
+        }
         return result;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public List<byte[]> readSequence() throws IOException {
int tag = readTag();
int tagNo = readTagNumber(tag);
if (tagNo != ASN1Encoder.SEQUENCE) {
throw new IOException("Invalid Sequence tag " + tagNo);
}
int length = readLength();
List<byte[]> result = new ArrayList<>();
while (length > 0) {
byte[] bytes = readNext();
result.add(bytes);
length = length - bytes.length;
}
return result;
}
public List<byte[]> readSequence() throws IOException {
int tag = readTag();
int tagNo = readTagNumber(tag);
if (tagNo != ASN1Encoder.SEQUENCE) {
throw new IOException("Invalid Sequence tag " + tagNo);
}
int length = readLength();
List<byte[]> result = new ArrayList<>();
while (length > 0) {
byte[] bytes = readNext();
if (bytes.length > length) {
throw new IOException("Sequence element length exceeds remaining sequence length");
}
result.add(bytes);
length = length - bytes.length;
}
if (length != 0) {
throw new IOException("Sequence length mismatch: " + length + " bytes remaining");
}
return result;
}
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java
between lines 49 and 63, the readSequence method subtracts bytes.length from
length without validating if length remains non-negative, risking negative
values with corrupted data. Add a check after subtracting bytes.length to ensure
length does not become negative, and if it does, throw an IOException indicating
corrupted or invalid ASN.1 data to prevent further processing.

Comment on lines +127 to +135
int readLength() throws IOException {
int length = read();
if (length < 0) {
throw new EOFException("EOF found when length expected");
}

if (length == 0x80) {
return -1; // indefinite-length encoding
}

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

Handle indefinite-length encoding or throw an exception.

The code detects indefinite-length encoding (0x80) but returns -1 without proper handling in calling methods.

     int readLength() throws IOException {
         int length = read();
         if (length < 0) {
             throw new EOFException("EOF found when length expected");
         }
 
         if (length == 0x80) {
-            return -1;      // indefinite-length encoding
+            throw new IOException("Indefinite-length encoding not supported");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
int readLength() throws IOException {
int length = read();
if (length < 0) {
throw new EOFException("EOF found when length expected");
}
if (length == 0x80) {
return -1; // indefinite-length encoding
}
int readLength() throws IOException {
int length = read();
if (length < 0) {
throw new EOFException("EOF found when length expected");
}
if (length == 0x80) {
throw new IOException("Indefinite-length encoding not supported");
}
// ... rest of method unchanged ...
}
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java
around lines 127 to 135, the method readLength detects indefinite-length
encoding by returning -1 but does not properly handle this case in the calling
methods. Update the code to either fully support indefinite-length encoding by
implementing the necessary logic or throw a specific exception when encountering
0x80 to prevent silent failures. Ensure that calling methods are updated
accordingly to handle this new behavior.

Comment on lines +184 to +193
void mark() {
count = 0;
is.mark(is.available());
}

int reset() {
int tmp = count;
is.reset();
return tmp;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add mark support validation.

The code uses mark/reset without checking if the stream supports marking.

     void mark() {
+        if (!is.markSupported()) {
+            throw new IllegalStateException("Mark not supported by the underlying stream");
+        }
         count = 0;
         is.mark(is.available());
     }
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/ASN1Decoder.java
between lines 184 and 193, the mark() and reset() methods use the input stream's
mark and reset functions without verifying if the stream supports marking. To
fix this, add a check using is.markSupported() before calling is.mark() and
is.reset(). If marking is not supported, handle it appropriately, such as
throwing an exception or avoiding the mark/reset calls to prevent runtime
errors.

Comment on lines +60 to +67
@Override
public Provider getBouncyCastleProvider() {
try {
return KeyStore.getInstance(KeyStore.getDefaultType()).getProvider();
} catch (KeyStoreException e) {
throw new IllegalStateException(e);
}
}

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

Misleading method name and implementation.

The getBouncyCastleProvider() method doesn't actually return a BouncyCastle provider - it returns the default KeyStore provider. This could cause confusion and unexpected behavior when cryptographic operations expect BouncyCastle-specific functionality.

Consider either:

  1. Renaming the method to reflect its actual behavior
  2. Implementing proper BouncyCastle provider detection
  3. Throwing UnsupportedOperationException like other unimplemented methods
 @Override
 public Provider getBouncyCastleProvider() {
-    try {
-        return KeyStore.getInstance(KeyStore.getDefaultType()).getProvider();
-    } catch (KeyStoreException e) {
-        throw new IllegalStateException(e);
-    }
+    throw new UnsupportedOperationException("BouncyCastle provider not supported in AuthzClient");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public Provider getBouncyCastleProvider() {
try {
return KeyStore.getInstance(KeyStore.getDefaultType()).getProvider();
} catch (KeyStoreException e) {
throw new IllegalStateException(e);
}
}
@Override
public Provider getBouncyCastleProvider() {
throw new UnsupportedOperationException("BouncyCastle provider not supported in AuthzClient");
}
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
between lines 60 and 67, the method getBouncyCastleProvider() misleadingly
returns the default KeyStore provider instead of a BouncyCastle provider. To fix
this, either rename the method to reflect it returns the default provider,
implement proper detection and return of the BouncyCastle provider, or throw an
UnsupportedOperationException to indicate the method is unimplemented,
consistent with other similar methods.

Comment on lines +103 to +122
public byte[] concatenatedRSToASN1DER(byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
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();
}

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

Critical bug in ECDSA signature conversion - multiple ASN1Encoder instances created.

The method creates multiple ASN1Encoder instances unnecessarily and the standalone write() calls on lines 114-115 are discarded, not contributing to the final result.

Apply this fix to use a single encoder instance properly:

 @Override
 public byte[] concatenatedRSToASN1DER(byte[] signature, int signLength) throws IOException {
     int len = signLength / 2;
     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();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public byte[] concatenatedRSToASN1DER(byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
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();
}
@Override
public byte[] concatenatedRSToASN1DER(byte[] signature, int signLength) throws IOException {
int len = signLength / 2;
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);
return ASN1Encoder.create()
.writeDerSeq(
ASN1Encoder.create().write(rBigInteger),
ASN1Encoder.create().write(sBigInteger))
.toByteArray();
}
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
lines 103 to 122, the method creates multiple ASN1Encoder instances and calls
write() on some that are discarded, causing a critical bug. Fix this by creating
a single ASN1Encoder instance at the start, use it to write both BigIntegers,
and then call writeDerSeq on that same instance before returning the byte array,
ensuring all writes contribute to the final encoded output.

Comment on lines +151 to +163
private byte[] integerToBytes(BigInteger s, int qLength) {
byte[] bytes = s.toByteArray();
if (qLength < bytes.length) {
byte[] tmp = new byte[qLength];
System.arraycopy(bytes, bytes.length - tmp.length, tmp, 0, tmp.length);
return tmp;
} else if (qLength > bytes.length) {
byte[] tmp = new byte[qLength];
System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length);
return tmp;
}
return bytes;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Integer to bytes conversion has potential truncation risk.

The method truncates bytes when qLength < bytes.length, which could lose significant bits if the BigInteger is larger than expected. This should be validated or throw an exception for invalid cases.

Add validation to prevent silent truncation:

 private byte[] integerToBytes(BigInteger s, int qLength) {
     byte[] bytes = s.toByteArray();
     if (qLength < bytes.length) {
+        // Check if we're only truncating leading zero bytes
+        for (int i = 0; i < bytes.length - qLength; i++) {
+            if (bytes[i] != 0) {
+                throw new IllegalArgumentException("BigInteger too large for target length");
+            }
+        }
         byte[] tmp = new byte[qLength];
         System.arraycopy(bytes, bytes.length - tmp.length, tmp, 0, tmp.length);
         return tmp;
     } else if (qLength > bytes.length) {
         byte[] tmp = new byte[qLength];
         System.arraycopy(bytes, 0, tmp, tmp.length - bytes.length, bytes.length);
         return tmp;
     }
     return bytes;
 }
🤖 Prompt for AI Agents
In
authz/client/src/main/java/org/keycloak/authorization/client/util/crypto/AuthzClientCryptoProvider.java
between lines 151 and 163, the integerToBytes method currently truncates the
byte array when qLength is less than the byte array length, risking loss of
significant bits. To fix this, add validation to check if qLength is smaller
than bytes.length and if so, throw an IllegalArgumentException or similar to
prevent silent truncation and ensure the caller handles this case explicitly.

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