1616 ******************************************************************************/
1717package org .mitre .openid .connect ;
1818
19+ import java .security .NoSuchAlgorithmException ;
20+ import java .security .spec .InvalidKeySpecException ;
1921import java .text .ParseException ;
2022import java .util .Collections ;
2123import java .util .HashMap ;
22- import java .util .List ;
2324import java .util .Map ;
2425import java .util .Set ;
2526
26- import net .minidev .json .JSONObject ;
27-
2827import org .mitre .jwt .signer .service .JwtSigningAndValidationService ;
28+ import org .mitre .jwt .signer .service .impl .DefaultJwtSigningAndValidationService ;
2929import org .mitre .jwt .signer .service .impl .JWKSetSigningAndValidationServiceCacheService ;
3030import org .mitre .oauth2 .model .ClientDetailsEntity ;
3131import org .mitre .oauth2 .service .ClientDetailsEntityService ;
3535import org .springframework .beans .factory .annotation .Autowired ;
3636import org .springframework .security .authentication .AuthenticationServiceException ;
3737import org .springframework .security .oauth2 .common .exceptions .InvalidClientException ;
38- import org .springframework .security .oauth2 .common .exceptions .InvalidScopeException ;
3938import org .springframework .security .oauth2 .common .util .OAuth2Utils ;
4039import org .springframework .security .oauth2 .provider .AuthorizationRequest ;
4140import org .springframework .security .oauth2 .provider .ClientDetails ;
4241import org .springframework .security .oauth2 .provider .DefaultOAuth2RequestFactory ;
4342import org .springframework .security .oauth2 .provider .OAuth2Request ;
4443import org .springframework .stereotype .Component ;
4544
46- import com .google .common .base .Joiner ;
47- import com .google .common .base .Splitter ;
4845import com .google .common .base .Strings ;
49- import com .google .common .collect .Iterables ;
50- import com .nimbusds .jose .util .JSONObjectUtils ;
46+ import com .google .common .collect .ImmutableMap ;
47+ import com .nimbusds .jose .Algorithm ;
48+ import com .nimbusds .jose .JWSAlgorithm ;
49+ import com .nimbusds .jose .jwk .JWK ;
50+ import com .nimbusds .jose .jwk .OctetSequenceKey ;
51+ import com .nimbusds .jose .jwk .Use ;
52+ import com .nimbusds .jose .util .Base64URL ;
53+ import com .nimbusds .jwt .JWT ;
54+ import com .nimbusds .jwt .JWTParser ;
55+ import com .nimbusds .jwt .PlainJWT ;
56+ import com .nimbusds .jwt .ReadOnlyJWTClaimsSet ;
5157import com .nimbusds .jwt .SignedJWT ;
5258
5359@ Component ("connectOAuth2RequestFactory" )
@@ -59,7 +65,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
5965
6066@ Autowired
6167private JWKSetSigningAndValidationServiceCacheService validators ;
62-
68+
6369@ Autowired
6470private SystemScopeService systemScopes ;
6571
@@ -129,32 +135,110 @@ private Map<String, String> processRequestObject(Map<String, String> inputParams
129135
130136// parse the request object
131137try {
132- SignedJWT jwsObject = SignedJWT .parse (jwtString );
133- JSONObject claims = jwsObject .getPayload ().toJSONObject ();
134-
135- // TODO: check parameter consistency, move keys to constants
136-
137- String clientId = JSONObjectUtils .getString (claims , "client_id" );
138- if (clientId != null ) {
139- parameters .put ("client_id" , clientId );
140- }
141-
142- ClientDetailsEntity client = clientDetailsService .loadClientByClientId (clientId );
143-
144- if (client .getJwksUri () == null ) {
145- throw new InvalidClientException ("Client must have a JWKS URI registered to use request objects." );
146- }
138+ JWT jwt = JWTParser .parse (jwtString );
147139
148- // check JWT signature
149- JwtSigningAndValidationService validator = validators .get (client .getJwksUri ());
150- if (validator == null ) {
151- throw new InvalidClientException ("Unable to create signature validator for client's JWKS URI: " + client .getJwksUri ());
140+ /*
141+ if (jwt instanceof EncryptedJWT) {
142+ // TODO: it's an encrypted JWT, decrypt it and use it
143+ } else {
144+ // it's not encrypted...
152145}
153-
154- if (!validator .validateSignature (jwsObject )) {
155- throw new AuthenticationServiceException ("Signature did not validate for presented JWT request object." );
146+ */
147+
148+
149+
150+
151+ // TODO: check parameter consistency, move keys to constants
152+
153+ if (jwt instanceof SignedJWT ) {
154+ // it's a signed JWT, check the signature
155+
156+ SignedJWT signedJwt = (SignedJWT )jwt ;
157+
158+ String clientId = inputParams .get ("client_id" );
159+ if (clientId == null ) {
160+ clientId = signedJwt .getJWTClaimsSet ().getStringClaim ("client_id" );
161+ }
162+
163+ ClientDetailsEntity client = clientDetailsService .loadClientByClientId (clientId );
164+
165+ if (client == null ) {
166+ throw new InvalidClientException ("Client not found: " + clientId );
167+ }
168+
169+
170+ JWSAlgorithm alg = signedJwt .getHeader ().getAlgorithm ();
171+
172+ if (client .getRequestObjectSigningAlg () != null ) {
173+ if (!client .getRequestObjectSigningAlg ().equals (alg )) {
174+ throw new AuthenticationServiceException ("Client's registered request object signing algorithm (" + client .getRequestObjectSigningAlg ().getAlgorithmName () + ") does not match request object's actual algorithm (" + alg .getName () + ")" );
175+ }
176+ }
177+
178+ if (alg .equals (JWSAlgorithm .RS256 )
179+ || alg .equals (JWSAlgorithm .RS384 )
180+ || alg .equals (JWSAlgorithm .RS512 )) {
181+
182+ // it's RSA, need to find the JWK URI and fetch the key
183+
184+ if (client .getJwksUri () == null ) {
185+ throw new InvalidClientException ("Client must have a JWKS URI registered to use signed request objects." );
186+ }
187+
188+ // check JWT signature
189+ JwtSigningAndValidationService validator = validators .get (client .getJwksUri ());
190+
191+ if (validator == null ) {
192+ throw new InvalidClientException ("Unable to create signature validator for client's JWKS URI: " + client .getJwksUri ());
193+ }
194+
195+ if (!validator .validateSignature (signedJwt )) {
196+ throw new AuthenticationServiceException ("Signature did not validate for presented JWT request object." );
197+ }
198+ } else if (alg .equals (JWSAlgorithm .HS256 )
199+ || alg .equals (JWSAlgorithm .HS384 )
200+ || alg .equals (JWSAlgorithm .HS512 )) {
201+
202+ // it's HMAC, we need to make a validator based on the client secret
203+
204+ JwtSigningAndValidationService validator = getSymmetricValidtor (client );
205+
206+ if (validator == null ) {
207+ throw new InvalidClientException ("Unable to create signature validator for client's secret: " + client .getClientSecret ());
208+ }
209+
210+ if (!validator .validateSignature (signedJwt )) {
211+ throw new AuthenticationServiceException ("Signature did not validate for presented JWT request object." );
212+ }
213+
214+
215+ }
216+
217+
218+ } else if (jwt instanceof PlainJWT ) {
219+ PlainJWT plainJwt = (PlainJWT )jwt ;
220+
221+ String clientId = inputParams .get ("client_id" );
222+ if (clientId == null ) {
223+ clientId = plainJwt .getJWTClaimsSet ().getStringClaim ("client_id" );
224+ }
225+
226+ ClientDetailsEntity client = clientDetailsService .loadClientByClientId (clientId );
227+
228+ if (client == null ) {
229+ throw new InvalidClientException ("Client not found: " + clientId );
230+ }
231+
232+ if (client .getRequestObjectSigningAlg () == null ) {
233+ throw new InvalidClientException ("Client is not registered for unsigned request objects (no request_object_signing_alg registered)" );
234+ } else if (!client .getRequestObjectSigningAlg ().getAlgorithm ().equals (Algorithm .NONE )) {
235+ throw new InvalidClientException ("Client is not registered for unsigned request objects (request_object_signing_alg is " + client .getRequestObjectSigningAlg ().getAlgorithmName () +")" );
236+ }
237+
238+ // if we got here, we're OK, keep processing
239+
156240}
157-
241+
158242/*
159243 * if (in Claims):
160244 * if (in params):
@@ -168,46 +252,53 @@ private Map<String, String> processRequestObject(Map<String, String> inputParams
168252 * we don't care
169253 */
170254
171- String responseTypes = JSONObjectUtils .getString (claims , "response_type" );
255+ ReadOnlyJWTClaimsSet claims = jwt .getJWTClaimsSet ();
256+
257+ String clientId = claims .getStringClaim ("client_id" );
258+ if (clientId != null ) {
259+ parameters .put ("client_id" , clientId );
260+ }
261+
262+ String responseTypes = claims .getStringClaim ("response_type" );
172263if (responseTypes != null ) {
173264parameters .put ("response_type" , responseTypes );
174265}
175266
176- if (claims .get ("redirect_uri" ) != null ) {
267+ if (claims .getStringClaim ("redirect_uri" ) != null ) {
177268if (inputParams .containsKey ("redirect_uri" ) == false ) {
178- parameters .put ("redirect_uri" , JSONObjectUtils . getString ( claims , "redirect_uri" ));
269+ parameters .put ("redirect_uri" , claims . getStringClaim ( "redirect_uri" ));
179270}
180271}
181272
182- String state = JSONObjectUtils . getString ( claims , "state" );
273+ String state = claims . getStringClaim ( "state" );
183274if (state != null ) {
184275if (inputParams .containsKey ("state" ) == false ) {
185276parameters .put ("state" , state );
186277}
187278}
188279
189- String nonce = JSONObjectUtils . getString ( claims , "nonce" );
280+ String nonce = claims . getStringClaim ( "nonce" );
190281if (nonce != null ) {
191282if (inputParams .containsKey ("nonce" ) == false ) {
192283parameters .put ("nonce" , nonce );
193284}
194285}
195286
196- String display = JSONObjectUtils . getString ( claims , "display" );
287+ String display = claims . getStringClaim ( "display" );
197288if (display != null ) {
198289if (inputParams .containsKey ("display" ) == false ) {
199290parameters .put ("display" , display );
200291}
201292}
202293
203- String prompt = JSONObjectUtils . getString ( claims , "prompt" );
294+ String prompt = claims . getStringClaim ( "prompt" );
204295if (prompt != null ) {
205296if (inputParams .containsKey ("prompt" ) == false ) {
206297parameters .put ("prompt" , prompt );
207298}
208299}
209300
210- String scope = JSONObjectUtils . getString ( claims , "scope" );
301+ String scope = claims . getStringClaim ( "scope" );
211302if (scope != null ) {
212303if (inputParams .containsKey ("scope" ) == false ) {
213304parameters .put ("scope" , scope );
@@ -219,4 +310,42 @@ private Map<String, String> processRequestObject(Map<String, String> inputParams
219310return parameters ;
220311}
221312
313+ /**
314+ * Create a symmetric signing and validation service for the given client
315+ *
316+ * @param client
317+ * @return
318+ */
319+ private JwtSigningAndValidationService getSymmetricValidtor (ClientDetailsEntity client ) {
320+
321+ if (client == null ) {
322+ logger .error ("Couldn't create symmetric validator for null client" );
323+ return null ;
324+ }
325+
326+ if (Strings .isNullOrEmpty (client .getClientSecret ())) {
327+ logger .error ("Couldn't create symmetric validator for client " + client .getClientId () + " without a client secret" );
328+ return null ;
329+ }
330+
331+ try {
332+
333+ JWK jwk = new OctetSequenceKey (new Base64URL (client .getClientSecret ()), Use .SIGNATURE , null , client .getClientId (), null , null , null );
334+ Map <String , JWK > keys = ImmutableMap .of (client .getClientId (), jwk );
335+ JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService (keys );
336+
337+ return service ;
338+
339+ } catch (NoSuchAlgorithmException e ) {
340+ // TODO Auto-generated catch block
341+ logger .error ("Couldn't create symmetric validator for client " + client .getClientId (), e );
342+ } catch (InvalidKeySpecException e ) {
343+ // TODO Auto-generated catch block
344+ logger .error ("Couldn't create symmetric validator for client " + client .getClientId (), e );
345+ }
346+
347+ return null ;
348+
349+ }
350+
222351}
0 commit comments