Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ public <T> T decodeClientJWT(String jwt, ClientModel client, BiConsumer<JOSE, Cl
jwtValidator.accept(jws, client);
return verifyJWS(client, clazz, (JWSInput) jws);
}
} catch (Exception ignore) {
// try to decrypt content as is
} catch (Exception e) {
logger.debug("Decrypted JWE content is not a valid JWS", e);
rejectUnsignedContentIfSignatureRequired(client);
}

return JsonSerialization.readValue(content, clazz);
Expand Down Expand Up @@ -381,4 +382,11 @@ public LogoutToken initLogoutToken(ClientModel client, UserModel user,

return token;
}

private void rejectUnsignedContentIfSignatureRequired(ClientModel client) {
String requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg();
if (requestedSignatureAlgorithm != null) {
throw new RuntimeException("Request object signature algorithm is required but decrypted JWE content is not a signed JWS");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1535,4 +1535,56 @@ private void assertRequestObjectEncryption(JWEHeader jweHeader) throws Exception
events.expectLogin().assertEvent();
}
}

/**
* When a client requires signed request objects, a JWE-encrypted request object whose
* decrypted content is raw JSON (not a nested JWS) must be rejected.
*/
@Test
public void testJweWithUnsignedJsonShouldBeRejectedWhenSignatureRequired() throws Exception {
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
ClientRepresentation clientRep = clientResource.toRepresentation();
try {
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(Algorithm.RS256);
clientResource.update(clientRep);

TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject =
new TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject();
requestObject.id(KeycloakModelUtils.generateId());
requestObject.iat(Long.valueOf(Time.currentTime()));
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.setClientId(oauth.getClientId());
requestObject.setResponseType("code");
requestObject.setRedirectUriParam(oauth.getRedirectUri());
requestObject.setScope("openid");
requestObject.setNonce(KeycloakModelUtils.generateId());

byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);

try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
OIDCConfigurationRepresentation representation = SimpleHttpDefault
.doGet(getAuthServerRoot().toString() + "realms/" + oauth.getRealm() + "/.well-known/openid-configuration",
httpClient).asJson(OIDCConfigurationRepresentation.class);
JSONWebKeySet jsonWebKeySet = SimpleHttpDefault.doGet(representation.getJwksUri(), httpClient).asJson(JSONWebKeySet.class);
Map<String, PublicKey> keysForUse = JWKSUtils.getKeysForUse(jsonWebKeySet, JWK.Use.ENCRYPTION);

KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils
.findActiveEncryptingKey(testRealm(), Algorithm.PS256);
PublicKey encryptionKey = keysForUse.get(encKey.getKid());

JWE jwe = new JWE().header(new JWEHeader(RSA_OAEP, JWEConstants.A256GCM, null)).content(contentBytes);
jwe.getKeyStorage().setEncryptionKey(encryptionKey);

oauth.loginForm().request(jwe.encodeJwe()).open();

assertTrue(errorPage.isCurrent());
assertEquals("Invalid Request", errorPage.getError());
}
} finally {
clientRep = clientResource.toRepresentation();
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(null);
clientResource.update(clientRep);
}
}
}
Loading