Skip to content

Commit 1c9dd06

Browse files
committed
new validations
1 parent 5f8afd2 commit 1c9dd06

File tree

3 files changed

+111
-3
lines changed

3 files changed

+111
-3
lines changed

src/main/java/es/in2/vcverifier/security/filters/CustomAuthenticationProvider.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@
3030
import org.springframework.security.core.AuthenticationException;
3131
import org.springframework.security.oauth2.core.*;
3232
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
33+
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
3334
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
3435
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
36+
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
37+
38+
import java.security.MessageDigest;
39+
import java.nio.charset.StandardCharsets;
40+
import java.util.Base64;
41+
import java.util.Objects;
3542
import org.springframework.security.oauth2.server.authorization.authentication.*;
3643
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
3744
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -75,6 +82,10 @@ private Authentication handleGrant(
7582
RegisteredClient registeredClient = getRegisteredClient(clientId);
7683
log.debug("CustomAuthenticationProvider -- handleGrant -- Registered client found: {}", registeredClient);
7784

85+
if (authentication instanceof OAuth2AuthorizationCodeAuthenticationToken authCodeToken) {
86+
validateAuthorizationCodePkceAndBinding(authCodeToken, clientId);
87+
}
88+
7889
Instant issueTime = Instant.now();
7990
Instant expirationTime = issueTime.plus(
8091
Long.parseLong(ACCESS_TOKEN_EXPIRATION_TIME),
@@ -123,9 +134,81 @@ private Authentication handleGrant(
123134
}
124135

125136
log.info("Authorization grant successfully processed");
137+
138+
if (authentication instanceof OAuth2AuthorizationCodeAuthenticationToken authCodeToken) {
139+
OAuth2Authorization authToRemove =
140+
oAuth2AuthorizationService.findByToken(authCodeToken.getCode(), new OAuth2TokenType(OAuth2ParameterNames.CODE));
141+
if (authToRemove != null) {
142+
oAuth2AuthorizationService.remove(authToRemove);
143+
}
144+
}
145+
126146
return new OAuth2AccessTokenAuthenticationToken(registeredClient, authentication, oAuth2AccessToken, oAuth2RefreshToken, additionalParameters);
127147
}
128148

149+
private void validateAuthorizationCodePkceAndBinding(OAuth2AuthorizationCodeAuthenticationToken authCodeToken,
150+
String requestedClientId) {
151+
final String code = authCodeToken.getCode();
152+
153+
OAuth2Authorization authorization =
154+
oAuth2AuthorizationService.findByToken(code, new OAuth2TokenType(OAuth2ParameterNames.CODE));
155+
if (authorization == null) {
156+
log.error("Authorization not found for code {}", code);
157+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
158+
}
159+
160+
String storedClientId = authorization.getAttribute(OAuth2ParameterNames.CLIENT_ID);
161+
String storedRedirect = authorization.getAttribute(OAuth2ParameterNames.REDIRECT_URI);
162+
String storedChallenge = authorization.getAttribute(PkceParameterNames.CODE_CHALLENGE);
163+
String storedMethod = authorization.getAttribute(PkceParameterNames.CODE_CHALLENGE_METHOD);
164+
165+
Map<String, Object> addl = authCodeToken.getAdditionalParameters();
166+
String reqRedirect = authCodeToken.getRedirectUri();
167+
if (reqRedirect == null && addl != null) {
168+
reqRedirect = (String) addl.get(OAuth2ParameterNames.REDIRECT_URI);
169+
}
170+
String codeVerifier = addl == null ? null : (String) addl.get(PkceParameterNames.CODE_VERIFIER);
171+
172+
if (!Objects.equals(storedClientId, requestedClientId)) {
173+
log.error("client_id binding mismatch. stored={}, requested={}", storedClientId, requestedClientId);
174+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
175+
}
176+
177+
if (!Objects.equals(storedRedirect, reqRedirect)) {
178+
log.error("redirect_uri binding mismatch. stored={}, requested={}", storedRedirect, reqRedirect);
179+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
180+
}
181+
182+
if (storedChallenge == null || storedMethod == null) {
183+
log.error("Missing PKCE attributes in stored authorization");
184+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
185+
}
186+
187+
if ("S256".equalsIgnoreCase(storedMethod)) {
188+
try {
189+
byte[] digest = MessageDigest.getInstance("SHA-256")
190+
.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
191+
String computed = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
192+
if (!computed.equals(storedChallenge)) {
193+
log.error("PKCE verification failed (S256)");
194+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
195+
}
196+
} catch (Exception e) {
197+
log.error("Error computing PKCE hash", e);
198+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
199+
}
200+
} else if ("plain".equalsIgnoreCase(storedMethod)) {
201+
if (!Objects.equals(codeVerifier, storedChallenge)) {
202+
log.error("PKCE verification failed (plain)");
203+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
204+
}
205+
} else {
206+
log.error("Unsupported PKCE method: {}", storedMethod);
207+
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
208+
}
209+
}
210+
211+
129212
private OAuth2RefreshToken getOAuth2RefreshToken(OAuth2AuthorizationGrantAuthenticationToken authentication, Instant issueTime, String clientId, JsonNode credentialJson, RegisteredClient registeredClient) {
130213
OAuth2RefreshToken oAuth2RefreshToken;
131214
oAuth2RefreshToken = generateRefreshToken(issueTime);

src/main/java/es/in2/vcverifier/security/filters/CustomTokenRequestConverter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import es.in2.vcverifier.service.ClientAssertionValidationService;
1717
import es.in2.vcverifier.service.JWTService;
1818
import es.in2.vcverifier.service.VpService;
19+
import io.micrometer.common.util.StringUtils;
1920
import jakarta.servlet.http.HttpServletRequest;
2021
import lombok.RequiredArgsConstructor;
2122
import lombok.extern.slf4j.Slf4j;
@@ -24,6 +25,7 @@
2425
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
2526
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
2627
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
28+
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
2729
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
2830
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
2931
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
@@ -106,6 +108,15 @@ private Authentication handleAuthorizationCodeGrant(MultiValueMap<String, String
106108
additionalParameters.put(NONCE, nonce);
107109
}
108110

111+
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
112+
String codeVerifier = parameters.getFirst(PkceParameterNames.CODE_VERIFIER);
113+
if (StringUtils.isNotBlank(codeVerifier)) {
114+
additionalParameters.put(PkceParameterNames.CODE_VERIFIER, codeVerifier);
115+
}
116+
if (StringUtils.isNotBlank(redirectUri)) {
117+
additionalParameters.put(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
118+
}
119+
109120
// Return the authentication token
110121
return new OAuth2AuthorizationCodeAuthenticationToken(code, clientPrincipal, null, additionalParameters);
111122
}

src/main/java/es/in2/vcverifier/service/impl/AuthorizationResponseProcessorServiceImpl.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
1616
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
1717
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
18+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
19+
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
20+
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
1821
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
1922
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
2023
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -27,9 +30,7 @@
2730
import java.text.ParseException;
2831
import java.time.Instant;
2932
import java.time.temporal.ChronoUnit;
30-
import java.util.Base64;
31-
import java.util.List;
32-
import java.util.UUID;
33+
import java.util.*;
3334

3435
import static es.in2.vcverifier.util.Constants.*;
3536
import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.NONCE;
@@ -95,6 +96,14 @@ public void processAuthResponse(String state, String vpToken){
9596
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
9697
}
9798

99+
String redirectUriUsed = oAuth2AuthorizationRequest.getRedirectUri();
100+
Set<String> requestedScopes = oAuth2AuthorizationRequest.getScopes();
101+
102+
Map<String, Object> addl = oAuth2AuthorizationRequest.getAdditionalParameters();
103+
String codeChallenge = (String) addl.get(PkceParameterNames.CODE_CHALLENGE);
104+
String codeChallengeMethod = (String) addl.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
105+
106+
98107
Instant expirationTime = issueTime.plus(Long.parseLong(ACCESS_TOKEN_EXPIRATION_TIME), ChronoUnit.valueOf(ACCESS_TOKEN_EXPIRATION_CHRONO_UNIT));
99108
// Register the Oauth2Authorization because is needed for verifications
100109
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
@@ -103,6 +112,11 @@ public void processAuthResponse(String state, String vpToken){
103112
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
104113
.token(new OAuth2AuthorizationCode(code, issueTime, expirationTime))
105114
.attribute(OAuth2AuthorizationRequest.class.getName(), oAuth2AuthorizationRequest)
115+
.attribute(PkceParameterNames.CODE_CHALLENGE, codeChallenge)
116+
.attribute(PkceParameterNames.CODE_CHALLENGE_METHOD, codeChallengeMethod)
117+
.attribute(OAuth2ParameterNames.REDIRECT_URI, redirectUriUsed)
118+
.attribute(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
119+
.attribute(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes))
106120
.build();
107121

108122
log.info("OAuth2Authorization generated");

0 commit comments

Comments
 (0)