Skip to content

Commit 032d41e

Browse files
committed
added JWKs-by-value support to client data model and API, closes mitreid-connect#826
1 parent 30162f6 commit 032d41e

14 files changed

+142
-25
lines changed

openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClient.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.nimbusds.jose.EncryptionMethod;
3232
import com.nimbusds.jose.JWEAlgorithm;
3333
import com.nimbusds.jose.JWSAlgorithm;
34+
import com.nimbusds.jose.jwk.JWKSet;
3435

3536
/**
3637
* @author jricher
@@ -460,6 +461,22 @@ public String getJwksUri() {
460461
public void setJwksUri(String jwksUri) {
461462
client.setJwksUri(jwksUri);
462463
}
464+
/**
465+
* @return
466+
* @see org.mitre.oauth2.model.ClientDetailsEntity#getJwks()
467+
*/
468+
public JWKSet getJwks() {
469+
return client.getJwks();
470+
}
471+
472+
/**
473+
* @param jwks
474+
* @see org.mitre.oauth2.model.ClientDetailsEntity#setJwks(com.nimbusds.jose.jwk.JWKSet)
475+
*/
476+
public void setJwks(JWKSet jwks) {
477+
client.setJwks(jwks);
478+
}
479+
463480
/**
464481
* @return
465482
* @see org.mitre.oauth2.model.ClientDetailsEntity#getSectorIdentifierUri()

openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public interface RegisteredClientFields {
2323
public String SECTOR_IDENTIFIER_URI = "sector_identifier_uri";
2424
public String APPLICATION_TYPE = "application_type";
2525
public String JWKS_URI = "jwks_uri";
26+
public String JWKS = "jwks";
2627
public String SCOPE_SEPARATOR = " ";
2728
public String POLICY_URI = "policy_uri";
2829
public String RESPONSE_TYPES = "response_types";

openid-connect-common/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@
2020
package org.mitre.openid.connect;
2121

2222

23+
import java.text.ParseException;
24+
2325
import org.mitre.oauth2.model.ClientDetailsEntity;
2426
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
2527
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
2628
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
2729
import org.mitre.oauth2.model.RegisteredClient;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
2832

2933
import com.google.common.base.Joiner;
3034
import com.google.common.base.Splitter;
3135
import com.google.common.collect.Sets;
3236
import com.google.gson.JsonElement;
3337
import com.google.gson.JsonObject;
3438
import com.google.gson.JsonParser;
39+
import com.nimbusds.jose.jwk.JWKSet;
3540

3641
import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
3742
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
@@ -49,6 +54,7 @@
4954
import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
5055
import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
5156
import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
57+
import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
5258
import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI;
5359
import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
5460
import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
@@ -78,11 +84,14 @@
7884
import static org.mitre.util.JsonUtils.getAsStringSet;
7985

8086
/**
87+
* Utility class to handle the parsing and serialization of ClientDetails objects.
88+
*
8189
* @author jricher
8290
*
8391
*/
8492
public class ClientDetailsEntityJsonProcessor {
8593

94+
private static Logger logger = LoggerFactory.getLogger(ClientDetailsEntityJsonProcessor.class);
8695

8796
private static JsonParser parser = new JsonParser();
8897

@@ -104,8 +113,6 @@ public static ClientDetailsEntity parse(JsonElement jsonEl) {
104113
JsonObject o = jsonEl.getAsJsonObject();
105114
ClientDetailsEntity c = new ClientDetailsEntity();
106115

107-
// TODO: make these field names into constants
108-
109116
// these two fields should only be sent in the update request, and MUST match existing values
110117
c.setClientId(getAsString(o, CLIENT_ID));
111118
c.setClientSecret(getAsString(o, CLIENT_SECRET));
@@ -133,7 +140,16 @@ public static ClientDetailsEntity parse(JsonElement jsonEl) {
133140
c.setResponseTypes(getAsStringSet(o, RESPONSE_TYPES));
134141
c.setPolicyUri(getAsString(o, POLICY_URI));
135142
c.setJwksUri(getAsString(o, JWKS_URI));
136-
143+
144+
JsonElement jwksEl = o.get(JWKS);
145+
if (jwksEl != null && jwksEl.isJsonObject()) {
146+
try {
147+
JWKSet jwks = JWKSet.parse(jwksEl.toString()); // we have to pass this through Nimbus's parser as a string
148+
c.setJwks(jwks);
149+
} catch (ParseException e) {
150+
logger.error("Unable to parse JWK Set for client", e);
151+
}
152+
}
137153

138154
// OIDC Additions
139155
String appType = getAsString(o, APPLICATION_TYPE);
@@ -261,6 +277,15 @@ public static JsonObject serialize(RegisteredClient c) {
261277
o.add(RESPONSE_TYPES, getAsArray(c.getResponseTypes()));
262278
o.addProperty(POLICY_URI, c.getPolicyUri());
263279
o.addProperty(JWKS_URI, c.getJwksUri());
280+
281+
// get the JWKS sub-object
282+
if (c.getJwks() != null) {
283+
// We have to re-parse it into GSON because Nimbus uses a different parser
284+
JsonElement jwks = parser.parse(c.getJwks().toString());
285+
o.add(JWKS, jwks);
286+
} else {
287+
o.add(JWKS, null);
288+
}
264289

265290
// OIDC Registration
266291
o.addProperty(APPLICATION_TYPE, c.getApplicationType() != null ? c.getApplicationType().getValue() : null);

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) {
114114

115115
// make sure that clients with the "refresh_token" grant type have the "offline_access" scope, and vice versa
116116
ensureRefreshTokenConsistency(client);
117+
118+
// make sure we don't have both a JWKS and a JWKS URI
119+
ensureKeyConsistency(client);
117120

118121
// timestamp this to right now
119122
client.setCreatedAt(new Date());
@@ -132,6 +135,16 @@ public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) {
132135
return c;
133136
}
134137

138+
/**
139+
* @param client
140+
*/
141+
private void ensureKeyConsistency(ClientDetailsEntity client) {
142+
if (client.getJwksUri() != null && client.getJwks() != null) {
143+
// a client can only have one key type or the other, not both
144+
throw new IllegalArgumentException("A client cannot have both JWKS URI and JWKS value");
145+
}
146+
}
147+
135148
private void ensureNoReservedScopes(ClientDetailsEntity client) {
136149
// make sure a client doesn't get any special system scopes
137150
Set<SystemScope> requestedScope = scopeService.fromStrings(client.getScope());
@@ -252,6 +265,9 @@ public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDet
252265
// if the client is flagged to allow for refresh tokens, make sure it's got the right scope
253266
ensureRefreshTokenConsistency(newClient);
254267

268+
// make sure we don't have both a JWKS and a JWKS URI
269+
ensureKeyConsistency(newClient);
270+
255271
// check the sector URI
256272
checkSectorIdentifierUri(newClient);
257273

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ private void processRequestObject(String jwtString, AuthorizationRequest request
217217

218218
// it's a public key, need to find the JWK URI and fetch the key
219219

220-
if (client.getJwksUri() == null) {
220+
if (Strings.isNullOrEmpty(client.getJwksUri()) && client.getJwks() == null) {
221221
throw new InvalidClientException("Client must have a JWKS registered to use signed request objects with a public key.");
222222
}
223223

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R
142142

143143
if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE)
144144
&& client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE)
145-
&& !Strings.isNullOrEmpty(client.getJwksUri())) {
145+
&& (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) {
146146

147147
JWTEncryptionAndDecryptionService encrypter = encrypters.getEncrypter(client.getJwksUri());
148148

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,8 @@ private void readClients(JsonReader reader) throws IOException {
720720
} else if (name.equals("subjectType")) {
721721
SubjectType st = SubjectType.getByValue(reader.nextString());
722722
client.setSubjectType(st);
723+
} else if (name.equals("jwks_uri")) {
724+
client.setJwksUri(reader.nextString());
723725
} else if (name.equals("requestObjectSigningAlg")) {
724726
JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString());
725727
client.setRequestObjectSigningAlg(alg);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,8 @@ private void readClients(JsonReader reader) throws IOException {
730730
} else if (name.equals("subjectType")) {
731731
SubjectType st = SubjectType.getByValue(reader.nextString());
732732
client.setSubjectType(st);
733+
} else if (name.equals("jwks_uri")) {
734+
client.setJwksUri(reader.nextString());
733735
} else if (name.equals("requestObjectSigningAlg")) {
734736
JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString());
735737
client.setRequestObjectSigningAlg(alg);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import com.nimbusds.jose.EncryptionMethod;
6060
import com.nimbusds.jose.JWEAlgorithm;
6161
import com.nimbusds.jose.JWSAlgorithm;
62+
import com.nimbusds.jose.jwk.JWKSet;
6263
import com.nimbusds.jwt.JWTParser;
6364

6465
import static org.mitre.util.JsonUtils.readMap;
@@ -387,6 +388,7 @@ private void writeClients(JsonWriter writer) {
387388
writer.endArray();
388389
writer.name("policyUri").value(client.getPolicyUri());
389390
writer.name("jwksUri").value(client.getJwksUri());
391+
writer.name("jwks").value((client.getJwks() != null) ? client.getJwks().toString() : null);
390392
writer.name("applicationType")
391393
.value((client.getApplicationType() != null) ? client.getApplicationType().getValue() : null);
392394
writer.name("sectorIdentifierUri").value(client.getSectorIdentifierUri());
@@ -1001,6 +1003,14 @@ private void readClients(JsonReader reader) throws IOException {
10011003
} else if (name.equals("subjectType")) {
10021004
SubjectType st = SubjectType.getByValue(reader.nextString());
10031005
client.setSubjectType(st);
1006+
} else if (name.equals("jwks_uri")) {
1007+
client.setJwksUri(reader.nextString());
1008+
} else if (name.equals("jwks")) {
1009+
try {
1010+
client.setJwks(JWKSet.parse(reader.nextString()));
1011+
} catch (ParseException e) {
1012+
logger.error("Couldn't parse JWK Set", e);
1013+
}
10041014
} else if (name.equals("requestObjectSigningAlg")) {
10051015
JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString());
10061016
client.setRequestObjectSigningAlg(alg);

openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@
3737
import com.google.gson.Gson;
3838
import com.google.gson.GsonBuilder;
3939
import com.google.gson.JsonElement;
40+
import com.google.gson.JsonParser;
4041
import com.google.gson.JsonPrimitive;
4142
import com.google.gson.JsonSerializationContext;
4243
import com.google.gson.JsonSerializer;
4344
import com.nimbusds.jose.EncryptionMethod;
4445
import com.nimbusds.jose.JWEAlgorithm;
4546
import com.nimbusds.jose.JWSAlgorithm;
47+
import com.nimbusds.jose.jwk.JWKSet;
4648

4749
/**
4850
*
@@ -60,6 +62,8 @@ public abstract class AbstractClientEntityView extends AbstractView {
6062
*/
6163
private static final Logger logger = LoggerFactory.getLogger(AbstractClientEntityView.class);
6264

65+
private JsonParser parser = new JsonParser();
66+
6367
private Gson gson = new GsonBuilder()
6468
.setExclusionStrategies(getExclusionStrategy())
6569
.registerTypeAdapter(JWSAlgorithm.class, new JsonSerializer<JWSAlgorithm>() {
@@ -92,6 +96,16 @@ public JsonElement serialize(EncryptionMethod src, Type typeOfSrc, JsonSerializa
9296
}
9397
}
9498
})
99+
.registerTypeAdapter(JWKSet.class, new JsonSerializer<JWKSet>() {
100+
@Override
101+
public JsonElement serialize(JWKSet src, Type typeOfSrc, JsonSerializationContext context) {
102+
if (src != null) {
103+
return parser.parse(src.toString());
104+
} else {
105+
return null;
106+
}
107+
}
108+
})
95109
.serializeNulls()
96110
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
97111
.create();

0 commit comments

Comments
 (0)