mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
fixes incorrect JWK thumprint computation
Closes #38394 Signed-off-by: Thomas Richner <thomas.richner@oviva.com>
This commit is contained in:
parent
e180a00229
commit
9920aa248e
@ -17,6 +17,7 @@
|
||||
|
||||
package org.keycloak.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
@ -33,6 +34,7 @@ import org.keycloak.jose.jws.crypto.HashUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -144,20 +146,30 @@ public class JWKSUtils {
|
||||
}
|
||||
|
||||
// TreeMap uses the natural ordering of the keys.
|
||||
// Therefore, it follows the way of hash value calculation for a public key defined by RFC 7678
|
||||
// Therefore, it follows the way of hash value calculation for a public key defined by RFC 7638
|
||||
public static String computeThumbprint(JWK key, String hashAlg) {
|
||||
Map<String, String> members = new TreeMap<>();
|
||||
members.put(JWK.KEY_TYPE, key.getKeyType());
|
||||
String kty = key.getKeyType();
|
||||
String[] requiredMembers = JWK_THUMBPRINT_REQUIRED_MEMBERS.get(kty);
|
||||
|
||||
for (String member : JWK_THUMBPRINT_REQUIRED_MEMBERS.get(key.getKeyType())) {
|
||||
members.put(member, (String) key.getOtherClaims().get(member));
|
||||
// e.g. `oct`, see RFC 7638 Section 3.2
|
||||
if (requiredMembers == null) {
|
||||
throw new UnsupportedOperationException("Unsupported key type: " + kty);
|
||||
}
|
||||
|
||||
Map<String, String> members = new TreeMap<>();
|
||||
members.put(JWK.KEY_TYPE, kty);
|
||||
|
||||
try {
|
||||
JsonNode node = JsonSerialization.writeValueAsNode(key);
|
||||
for (String member : requiredMembers) {
|
||||
members.put(member, node.get(member).asText());
|
||||
}
|
||||
|
||||
byte[] bytes = JsonSerialization.writeValueAsBytes(members);
|
||||
byte[] hash = HashUtils.hash(hashAlg, bytes);
|
||||
return Base64Url.encode(hash);
|
||||
} catch (IOException ex) {
|
||||
logger.debugf(ex, "Failed to compute JWK thumbprint for key '%s'.", key.getKeyId());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,10 @@ public class JsonSerialization {
|
||||
return mapper.writeValueAsBytes(obj);
|
||||
}
|
||||
|
||||
public static JsonNode writeValueAsNode(Object obj) {
|
||||
return mapper.valueToTree(obj);
|
||||
}
|
||||
|
||||
public static <T> T readValue(byte[] bytes, Class<T> type) throws IOException {
|
||||
return mapper.readValue(bytes, type);
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import org.junit.Test;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.crypto.KeyWrapper;
|
||||
import org.keycloak.crypto.PublicKeysWrapper;
|
||||
import org.keycloak.jose.jwk.ECPublicJWK;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.rule.CryptoInitRule;
|
||||
@ -29,6 +30,7 @@ import org.keycloak.rule.CryptoInitRule;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
|
||||
public abstract class JWKSUtilsTest {
|
||||
@ -36,6 +38,35 @@ public abstract class JWKSUtilsTest {
|
||||
@ClassRule
|
||||
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
|
||||
|
||||
@Test
|
||||
public void publicEcMatches() throws Exception {
|
||||
String keyA = "{" +
|
||||
" \"kty\": \"EC\"," +
|
||||
" \"use\": \"sig\"," +
|
||||
" \"crv\": \"P-384\"," +
|
||||
" \"kid\": \"key-a\"," +
|
||||
" \"x\": \"KVZ5h_W0-8fXmUrxmyRpO_9vwwI7urXfyxGdxm1hpEuhPj2hhDxivnb2BhNvtC6O\"," +
|
||||
" \"y\": \"1J3JVw_zR3uB3biAE7fs3V_4tJy2M1JinzWj9a4je5GSoW6zgGV4bk85OcuyUAhj\"," +
|
||||
" \"alg\": \"ES384\"" +
|
||||
" }";
|
||||
|
||||
ECPublicJWK ecPublicKey = JsonSerialization.readValue(keyA, ECPublicJWK.class);
|
||||
JWK publicKey = JsonSerialization.readValue(keyA, JWK.class);
|
||||
|
||||
assertEquals(JWKSUtils.computeThumbprint(publicKey), JWKSUtils.computeThumbprint(ecPublicKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedKeyType() throws Exception {
|
||||
String keyA = "{" +
|
||||
" \"kty\": \"OCT\"," +
|
||||
" \"use\": \"sig\"" +
|
||||
" }";
|
||||
|
||||
JWK publicKey = JsonSerialization.readValue(keyA, JWK.class);
|
||||
assertThrows(UnsupportedOperationException.class, () -> JWKSUtils.computeThumbprint(publicKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicRs256() throws Exception {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user