1919 */
2020package 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 ;
2229import java .util .Collection ;
2330import java .util .Date ;
2431import java .util .HashSet ;
3037import org .mitre .oauth2 .model .ClientDetailsEntity ;
3138import org .mitre .oauth2 .model .OAuth2AccessTokenEntity ;
3239import org .mitre .oauth2 .model .OAuth2RefreshTokenEntity ;
40+ import org .mitre .oauth2 .model .PKCEAlgorithm ;
3341import org .mitre .oauth2 .model .SystemScope ;
3442import org .mitre .oauth2 .repository .AuthenticationHolderRepository ;
3543import org .mitre .oauth2 .repository .OAuth2TokenRepository ;
4452import org .springframework .security .authentication .AuthenticationCredentialsNotFoundException ;
4553import org .springframework .security .core .AuthenticationException ;
4654import org .springframework .security .oauth2 .common .exceptions .InvalidClientException ;
55+ import org .springframework .security .oauth2 .common .exceptions .InvalidRequestException ;
4756import org .springframework .security .oauth2 .common .exceptions .InvalidScopeException ;
4857import org .springframework .security .oauth2 .common .exceptions .InvalidTokenException ;
49- import org .springframework .security .oauth2 .provider .ClientAlreadyExistsException ;
5058import org .springframework .security .oauth2 .provider .OAuth2Authentication ;
5159import org .springframework .security .oauth2 .provider .OAuth2Request ;
5260import org .springframework .security .oauth2 .provider .TokenRequest ;
5361import org .springframework .security .oauth2 .provider .token .TokenEnhancer ;
5462import org .springframework .stereotype .Service ;
5563
5664import com .google .common .collect .Sets ;
65+ import com .nimbusds .jose .util .Base64URL ;
5766import com .nimbusds .jwt .JWTClaimsSet ;
5867import 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 );
0 commit comments