Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion core/src/main/java/org/keycloak/jose/jwk/JWK.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,14 @@ public void setOtherClaims(String name, Object value) {
@JsonIgnore
public <T> T getOtherClaim(String claimName, Class<T> claimType) {
Object o = getOtherClaims().get(claimName);
return o == null ? null : claimType.cast(o);
if (o == null) {
return null;
}
try {
return claimType.cast(o);
} catch (ClassCastException e) {
return null;
Comment thread
Awambeng marked this conversation as resolved.
}
}

}
66 changes: 66 additions & 0 deletions core/src/test/java/org/keycloak/jose/jwk/JWKOtherClaimTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.keycloak.jose.jwk;

import java.util.ArrayList;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

/**
* Tests for {@link JWK#getOtherClaim(String, Class)} type safety.
* <p>
* Verifies that the method gracefully handles type mismatches in the otherClaims map
* instead of throwing {@link ClassCastException}.
*/
public class JWKOtherClaimTest {

@Test
public void getOtherClaimReturnsValueWhenTypeMatches() {
JWK jwk = new JWK();
jwk.setOtherClaims("crv", "P-256");

String result = jwk.getOtherClaim("crv", String.class);

assertEquals("P-256", result);
}

@Test
public void getOtherClaimReturnsNullWhenClaimIsAbsent() {
JWK jwk = new JWK();

String result = jwk.getOtherClaim("crv", String.class);

assertNull(result);
}

@Test
public void getOtherClaimReturnsNullWhenValueIsWrongType() {
JWK jwk = new JWK();
jwk.setOtherClaims("crv", new ArrayList<>());

String result = jwk.getOtherClaim("crv", String.class);

assertNull(result);
}

@Test
public void getOtherClaimNullValueReturnsNull() {
JWK jwk = new JWK();
jwk.setOtherClaims("crv", null);

String result = jwk.getOtherClaim("crv", String.class);

assertNull(result);
}

@Test
public void getOtherClaimIntegerValueRetrievedAsInteger() {
JWK jwk = new JWK();
jwk.setOtherClaims("x", 42);

Integer result = jwk.getOtherClaim("x", Integer.class);

assertEquals(Integer.valueOf(42), result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ protected SignatureVerifierContext getVerifier(JWK jwk, String jwsAlgorithm) thr
return signatureProvider.verifier(keyWrapper);
}

private KeyWrapper getKeyWrapper(JWK jwk, String algorithm) {
private KeyWrapper getKeyWrapper(JWK jwk, String algorithm) throws VerificationException {
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setType(jwk.getKeyType());

Expand All @@ -56,7 +56,11 @@ private KeyWrapper getKeyWrapper(JWK jwk, String algorithm) {
}

JWKParser parser = JWKParser.create(jwk);
keyWrapper.setPublicKey(parser.toPublicKey());
try {
keyWrapper.setPublicKey(parser.toPublicKey());
} catch (RuntimeException e) {
throw new VerificationException("Failed to parse JWK into a public key", e);
}
return keyWrapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,40 @@ public void testRequestCredentialWithPrivateJwkMaterialInHeaderRejected() {
assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError());
}

@Test
public void testRequestCredentialWithMalformedJwkCrvRejected() {
final String scopeName = jwtTypeCredentialScope.getName();
String credConfigId = jwtTypeCredentialScope.getAttributes().get(CredentialScopeModel.VC_CONFIGURATION_ID);

CredentialIssuer credentialIssuer = getCredentialIssuerMetadata();
OID4VCAuthorizationDetail authDetail = new OID4VCAuthorizationDetail();
authDetail.setType(OPENID_CREDENTIAL);
authDetail.setCredentialConfigurationId(credConfigId);
authDetail.setLocations(List.of(credentialIssuer.getCredentialIssuer()));

String authCode = getAuthorizationCode(oauth, client, "john", scopeName);
AccessTokenResponse tokenResponse = getBearerToken(oauth, authCode, authDetail);
String token = tokenResponse.getAccessToken();
String credentialIdentifier = tokenResponse.getOID4VCAuthorizationDetails().get(0).getCredentialIdentifiers().get(0);
String cNonce = getCNonce();

String issuer = credentialIssuer.getCredentialIssuer();
String validJwtProof = generateJwtProof(issuer, cNonce);
String malformedCrvProof = withMalformedJwkCrvInHeader(validJwtProof);

CredentialRequest request = new CredentialRequest()
.setCredentialIdentifier(credentialIdentifier)
.setProofs(new Proofs().setJwt(List.of(malformedCrvProof)));

Oid4vcCredentialResponse response = oauth.oid4vc()
.credentialRequest(request)
.bearerToken(token)
.send();

assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals(ErrorType.INVALID_PROOF.getValue(), response.getError());
}

@Test
public void testRequestCredentialWithMissingIssuerInClientBoundFlowAllowed() {
final String scopeName = jwtTypeCredentialScope.getName();
Expand Down Expand Up @@ -1857,4 +1891,18 @@ private static String withPrivateJwkMaterialInHeader(String jwt) {
}
}

private static String withMalformedJwkCrvInHeader(String jwt) {
try {
String[] parts = jwt.split("\\.");
Map<String, Object> header = JsonSerialization.readValue(Base64Url.decode(parts[0]), new TypeReference<>() {
});
// Replace the jwk with a malformed one where crv is an empty array instead of a string
header.put("jwk", Map.of("crv", List.of()));
parts[0] = Base64Url.encode(JsonSerialization.writeValueAsString(header).getBytes(StandardCharsets.UTF_8));
return String.join(".", parts);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}
Loading