From 195e4a4116d48a3f65bd14e248e893eac22792a9 Mon Sep 17 00:00:00 2001 From: David Kowis Date: Thu, 8 May 2025 12:35:59 -0500 Subject: [PATCH 1/2] Fix JWK Thumbprint calculation to conform to RFC7638 Just used the nimbus JOSE library to do it, because it already has a compliant implementation. Signed-off-by: David Kowis --- .../authentication/DPoPAuthenticationProvider.java | 11 ++++------- .../DPoPAuthenticationProviderTests.java | 13 +++++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java index b26cb754c70..5e7ca1e0c73 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProvider.java @@ -193,25 +193,22 @@ public OAuth2TokenValidatorResult validate(Jwt jwt) { return OAuth2TokenValidatorResult.failure(error); } - PublicKey publicKey = null; + JWK jwk = null; @SuppressWarnings("unchecked") Map jwkJson = (Map) jwt.getHeaders().get("jwk"); try { - JWK jwk = JWK.parse(jwkJson); - if (jwk instanceof AsymmetricJWK) { - publicKey = ((AsymmetricJWK) jwk).toPublicKey(); - } + jwk = JWK.parse(jwkJson); } catch (Exception ignored) { } - if (publicKey == null) { + if (jwk == null) { OAuth2Error error = createOAuth2Error("jwk header is missing or invalid."); return OAuth2TokenValidatorResult.failure(error); } String jwkThumbprint; try { - jwkThumbprint = computeSHA256(publicKey); + jwkThumbprint = jwk.computeThumbprint().toString(); } catch (Exception ex) { OAuth2Error error = createOAuth2Error("Failed to compute SHA-256 Thumbprint for jwk."); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java index 08aec389000..670f0a03765 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.UUID; +import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; @@ -218,8 +219,8 @@ public void authenticateWhenJktMissingThenThrowOAuth2AuthenticationException() t @Test public void authenticateWhenJktDoesNotMatchThenThrowOAuth2AuthenticationException() throws Exception { - // Use different client public key - Jwt accessToken = generateAccessToken(TestKeys.DEFAULT_EC_KEY_PAIR.getPublic()); + // Use different jwk to make it not match + Jwt accessToken = generateAccessToken(TestJwks.DEFAULT_EC_JWK); JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(accessToken); given(this.tokenAuthenticationManager.authenticate(any())).willReturn(jwtAuthenticationToken); @@ -285,14 +286,14 @@ public void authenticateWhenDPoPProofValidThenSuccess() throws Exception { } private Jwt generateAccessToken() { - return generateAccessToken(TestKeys.DEFAULT_PUBLIC_KEY); + return generateAccessToken(TestJwks.DEFAULT_RSA_JWK); } - private Jwt generateAccessToken(PublicKey clientPublicKey) { + private Jwt generateAccessToken(JWK clientJwk) { Map jktClaim = null; - if (clientPublicKey != null) { + if (clientJwk != null) { try { - String sha256Thumbprint = computeSHA256(clientPublicKey); + String sha256Thumbprint = clientJwk.computeThumbprint().toString(); jktClaim = new HashMap<>(); jktClaim.put("jkt", sha256Thumbprint); } From 45f52323876c4430fe7038de70f921a53dc3e901 Mon Sep 17 00:00:00 2001 From: David Kowis Date: Thu, 8 May 2025 12:35:59 -0500 Subject: [PATCH 2/2] Removed unused hash calculation The other method remains for the `ath` claims Signed-off-by: David Kowis --- .../authentication/DPoPAuthenticationProviderTests.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java index 670f0a03765..bc2d83101e8 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/DPoPAuthenticationProviderTests.java @@ -322,11 +322,4 @@ private static String computeSHA256(String value) throws Exception { byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } - - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - }