Skip to content

Commit c9aa42d

Browse files
author
Justin Richer
committed
better processing for signed request objects
1 parent f9ca151 commit c9aa42d

File tree

1 file changed

+169
-40
lines changed

1 file changed

+169
-40
lines changed

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

Lines changed: 169 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
******************************************************************************/
1717
package org.mitre.openid.connect;
1818

19+
import java.security.NoSuchAlgorithmException;
20+
import java.security.spec.InvalidKeySpecException;
1921
import java.text.ParseException;
2022
import java.util.Collections;
2123
import java.util.HashMap;
22-
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Set;
2526

26-
import net.minidev.json.JSONObject;
27-
2827
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
28+
import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService;
2929
import org.mitre.jwt.signer.service.impl.JWKSetSigningAndValidationServiceCacheService;
3030
import org.mitre.oauth2.model.ClientDetailsEntity;
3131
import org.mitre.oauth2.service.ClientDetailsEntityService;
@@ -35,19 +35,25 @@
3535
import org.springframework.beans.factory.annotation.Autowired;
3636
import org.springframework.security.authentication.AuthenticationServiceException;
3737
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
38-
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
3938
import org.springframework.security.oauth2.common.util.OAuth2Utils;
4039
import org.springframework.security.oauth2.provider.AuthorizationRequest;
4140
import org.springframework.security.oauth2.provider.ClientDetails;
4241
import org.springframework.security.oauth2.provider.DefaultOAuth2RequestFactory;
4342
import org.springframework.security.oauth2.provider.OAuth2Request;
4443
import org.springframework.stereotype.Component;
4544

46-
import com.google.common.base.Joiner;
47-
import com.google.common.base.Splitter;
4845
import 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;
5157
import com.nimbusds.jwt.SignedJWT;
5258

5359
@Component("connectOAuth2RequestFactory")
@@ -59,7 +65,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
5965

6066
@Autowired
6167
private JWKSetSigningAndValidationServiceCacheService validators;
62-
68+
6369
@Autowired
6470
private SystemScopeService systemScopes;
6571

@@ -129,32 +135,110 @@ private Map<String, String> processRequestObject(Map<String, String> inputParams
129135

130136
// parse the request object
131137
try {
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");
172263
if (responseTypes != null) {
173264
parameters.put("response_type", responseTypes);
174265
}
175266

176-
if (claims.get("redirect_uri") != null) {
267+
if (claims.getStringClaim("redirect_uri") != null) {
177268
if (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");
183274
if(state != null) {
184275
if (inputParams.containsKey("state") == false) {
185276
parameters.put("state", state);
186277
}
187278
}
188279

189-
String nonce = JSONObjectUtils.getString(claims, "nonce");
280+
String nonce = claims.getStringClaim("nonce");
190281
if(nonce != null) {
191282
if (inputParams.containsKey("nonce") == false) {
192283
parameters.put("nonce", nonce);
193284
}
194285
}
195286

196-
String display = JSONObjectUtils.getString(claims, "display");
287+
String display = claims.getStringClaim("display");
197288
if (display != null) {
198289
if (inputParams.containsKey("display") == false) {
199290
parameters.put("display", display);
200291
}
201292
}
202293

203-
String prompt = JSONObjectUtils.getString(claims, "prompt");
294+
String prompt = claims.getStringClaim("prompt");
204295
if (prompt != null) {
205296
if (inputParams.containsKey("prompt") == false) {
206297
parameters.put("prompt", prompt);
207298
}
208299
}
209300

210-
String scope = JSONObjectUtils.getString(claims, "scope");
301+
String scope = claims.getStringClaim("scope");
211302
if (scope != null) {
212303
if (inputParams.containsKey("scope") == false) {
213304
parameters.put("scope", scope);
@@ -219,4 +310,42 @@ private Map<String, String> processRequestObject(Map<String, String> inputParams
219310
return 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

Comments
 (0)