Skip to content

Commit ac0cafe

Browse files
committed
parse and process PKCE requests
1 parent 5dcda28 commit ac0cafe

File tree

3 files changed

+61
-7
lines changed

3 files changed

+61
-7
lines changed

openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
*/
2020
package org.mitre.oauth2.service.impl;
2121

22+
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE;
23+
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD;
24+
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER;
25+
26+
import java.nio.charset.StandardCharsets;
27+
import java.security.MessageDigest;
28+
import java.security.NoSuchAlgorithmException;
2229
import java.util.Collection;
2330
import java.util.Date;
2431
import java.util.HashSet;
@@ -30,6 +37,7 @@
3037
import org.mitre.oauth2.model.ClientDetailsEntity;
3138
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
3239
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
40+
import org.mitre.oauth2.model.PKCEAlgorithm;
3341
import org.mitre.oauth2.model.SystemScope;
3442
import org.mitre.oauth2.repository.AuthenticationHolderRepository;
3543
import org.mitre.oauth2.repository.OAuth2TokenRepository;
@@ -44,16 +52,17 @@
4452
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
4553
import org.springframework.security.core.AuthenticationException;
4654
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
55+
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
4756
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
4857
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
49-
import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;
5058
import org.springframework.security.oauth2.provider.OAuth2Authentication;
5159
import org.springframework.security.oauth2.provider.OAuth2Request;
5260
import org.springframework.security.oauth2.provider.TokenRequest;
5361
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
5462
import org.springframework.stereotype.Service;
5563

5664
import com.google.common.collect.Sets;
65+
import com.nimbusds.jose.util.Base64URL;
5766
import com.nimbusds.jwt.JWTClaimsSet;
5867
import com.nimbusds.jwt.PlainJWT;
5968

@@ -169,14 +178,43 @@ private OAuth2RefreshTokenEntity clearExpiredRefreshToken(OAuth2RefreshTokenEnti
169178
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
170179
if (authentication != null && authentication.getOAuth2Request() != null) {
171180
// look up our client
172-
OAuth2Request clientAuth = authentication.getOAuth2Request();
181+
OAuth2Request request = authentication.getOAuth2Request();
173182

174-
ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
183+
ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId());
175184

176185
if (client == null) {
177-
throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
186+
throw new InvalidClientException("Client not found: " + request.getClientId());
187+
}
188+
189+
190+
// handle the PKCE code challenge if present
191+
if (request.getExtensions().containsKey(CODE_CHALLENGE)) {
192+
String challenge = (String) request.getExtensions().get(CODE_CHALLENGE);
193+
PKCEAlgorithm alg = PKCEAlgorithm.parse((String) request.getExtensions().get(CODE_CHALLENGE_METHOD));
194+
195+
String verifier = request.getRequestParameters().get(CODE_VERIFIER);
196+
197+
if (alg.equals(PKCEAlgorithm.plain)) {
198+
// do a direct string comparison
199+
if (!challenge.equals(verifier)) {
200+
throw new InvalidRequestException("Code challenge and verifier do not match");
201+
}
202+
} else if (alg.equals(PKCEAlgorithm.S256)) {
203+
// hash the verifier
204+
try {
205+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
206+
String hash = Base64URL.encode(digest.digest(verifier.getBytes(StandardCharsets.US_ASCII))).toString();
207+
if (!challenge.equals(hash)) {
208+
throw new InvalidRequestException("Code challenge and verifier do not match");
209+
}
210+
} catch (NoSuchAlgorithmException e) {
211+
logger.error("Unknown algorithm for PKCE digest", e);
212+
}
213+
}
214+
178215
}
179216

217+
180218
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();//accessTokenFactory.createNewAccessToken();
181219

182220
// attach the client
@@ -185,7 +223,7 @@ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentica
185223
// inherit the scope from the auth, but make a new set so it is
186224
//not unmodifiable. Unmodifiables don't play nicely with Eclipselink, which
187225
//wants to use the clone operation.
188-
Set<SystemScope> scopes = scopeService.fromStrings(clientAuth.getScope());
226+
Set<SystemScope> scopes = scopeService.fromStrings(request.getScope());
189227

190228
// remove any of the special system scopes
191229
scopes = scopeService.removeReservedScopes(scopes);

openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.mitre.openid.connect.request;
1818

1919

20-
import static org.mitre.openid.connect.request.ConnectRequestParameters.AUD;
20+
import static org.mitre.openid.connect.request.ConnectRequestParameters.*;
2121
import static org.mitre.openid.connect.request.ConnectRequestParameters.CLAIMS;
2222
import static org.mitre.openid.connect.request.ConnectRequestParameters.CLIENT_ID;
2323
import static org.mitre.openid.connect.request.ConnectRequestParameters.DISPLAY;
@@ -41,6 +41,7 @@
4141
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
4242
import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
4343
import org.mitre.oauth2.model.ClientDetailsEntity;
44+
import org.mitre.oauth2.model.PKCEAlgorithm;
4445
import org.mitre.oauth2.service.ClientDetailsEntityService;
4546
import org.mitre.oauth2.service.SystemScopeService;
4647
import org.slf4j.Logger;
@@ -138,6 +139,16 @@ public AuthorizationRequest createAuthorizationRequest(Map<String, String> input
138139
request.getExtensions().put(AUD, inputParams.get(AUD));
139140
}
140141

142+
if (inputParams.containsKey(CODE_CHALLENGE)) {
143+
request.getExtensions().put(CODE_CHALLENGE, inputParams.get(CODE_CHALLENGE));
144+
if (inputParams.containsKey(CODE_CHALLENGE_METHOD)) {
145+
request.getExtensions().put(CODE_CHALLENGE_METHOD, inputParams.get(CODE_CHALLENGE_METHOD));
146+
} else {
147+
// if the client doesn't specify a code challenge transformation method, it's "plain"
148+
request.getExtensions().put(CODE_CHALLENGE_METHOD, PKCEAlgorithm.plain.getName());
149+
}
150+
151+
}
141152

142153
if (inputParams.containsKey(REQUEST)) {
143154
request.getExtensions().put(REQUEST, inputParams.get(REQUEST));

openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,10 @@ public interface ConnectRequestParameters {
4646

4747
// audience
4848
public String AUD = "aud";
49-
49+
50+
// PKCE
51+
public String CODE_CHALLENGE = "code_challenge";
52+
public String CODE_CHALLENGE_METHOD = "code_challenge_method";
53+
public String CODE_VERIFIER = "code_verifier";
54+
5055
}

0 commit comments

Comments
 (0)