[KEYCLOAK-18630] - Request object encryption support
This commit is contained in:
parent
6686482ba5
commit
1baab67f3b
36 changed files with 579 additions and 100 deletions
16
core/src/main/java/org/keycloak/jose/JOSE.java
Normal file
16
core/src/main/java/org/keycloak/jose/JOSE.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package org.keycloak.jose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to represent signed (JWS) and encrypted (JWE) JWTs.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public interface JOSE {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JWT header.
|
||||||
|
*
|
||||||
|
* @return the JWT header
|
||||||
|
*/
|
||||||
|
<H extends JOSEHeader> H getHeader();
|
||||||
|
}
|
22
core/src/main/java/org/keycloak/jose/JOSEHeader.java
Normal file
22
core/src/main/java/org/keycloak/jose/JOSEHeader.java
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package org.keycloak.jose;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface represents a JOSE header.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public interface JOSEHeader extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the algorithm used to sign or encrypt the JWT from the JOSE header.
|
||||||
|
*
|
||||||
|
* @return the algorithm from the JOSE header
|
||||||
|
*/
|
||||||
|
String getRawAlgorithm();
|
||||||
|
|
||||||
|
String getKeyId();
|
||||||
|
}
|
49
core/src/main/java/org/keycloak/jose/JOSEParser.java
Normal file
49
core/src/main/java/org/keycloak/jose/JOSEParser.java
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package org.keycloak.jose;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.jose.jwe.JWE;
|
||||||
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||||
|
*/
|
||||||
|
public class JOSEParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given encoded {@code jwt} and returns either a {@link JWSInput} or {@link JWE}
|
||||||
|
* depending on the JOSE header configuration.
|
||||||
|
*
|
||||||
|
* @param jwt the encoded JWT
|
||||||
|
* @return a {@link JOSE}
|
||||||
|
*/
|
||||||
|
public static JOSE parse(String jwt) {
|
||||||
|
String[] parts = jwt.split("\\.");
|
||||||
|
|
||||||
|
if (parts.length == 0) {
|
||||||
|
throw new RuntimeException("Could not infer header from JWT");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode header;
|
||||||
|
|
||||||
|
try {
|
||||||
|
header = JsonSerialization.readValue(Base64Url.decode(parts[0]), JsonNode.class);
|
||||||
|
} catch (IOException cause) {
|
||||||
|
throw new RuntimeException("Failed to parse JWT header", cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.has("enc")) {
|
||||||
|
return new JWE(jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new JWSInput(jwt);
|
||||||
|
} catch (JWSInputException cause) {
|
||||||
|
throw new RuntimeException("Failed to build JWS", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ import java.security.spec.KeySpec;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
import org.keycloak.common.util.BouncyIntegration;
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
import org.keycloak.jose.JOSEHeader;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
@ -36,7 +38,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class JWE {
|
public class JWE implements JOSE {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
BouncyIntegration.init();
|
BouncyIntegration.init();
|
||||||
|
@ -55,13 +57,20 @@ public class JWE {
|
||||||
|
|
||||||
private byte[] authenticationTag;
|
private byte[] authenticationTag;
|
||||||
|
|
||||||
|
public JWE() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWE(String jwt) {
|
||||||
|
setupJWEHeader(jwt);
|
||||||
|
}
|
||||||
|
|
||||||
public JWE header(JWEHeader header) {
|
public JWE header(JWEHeader header) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
this.base64Header = null;
|
this.base64Header = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
JWEHeader getHeader() {
|
public JOSEHeader getHeader() {
|
||||||
if (header == null && base64Header != null) {
|
if (header == null && base64Header != null) {
|
||||||
try {
|
try {
|
||||||
byte[] decodedHeader = Base64Url.decode(base64Header);
|
byte[] decodedHeader = Base64Url.decode(base64Header);
|
||||||
|
@ -181,7 +190,7 @@ public class JWE {
|
||||||
this.encryptedContent = Base64Url.decode(parts[3]);
|
this.encryptedContent = Base64Url.decode(parts[3]);
|
||||||
this.authenticationTag = Base64Url.decode(parts[4]);
|
this.authenticationTag = Base64Url.decode(parts[4]);
|
||||||
|
|
||||||
this.header = getHeader();
|
this.header = (JWEHeader) getHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JWE getProcessedJWE(JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws Exception {
|
private JWE getProcessedJWE(JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws Exception {
|
||||||
|
@ -206,7 +215,7 @@ public class JWE {
|
||||||
public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
|
public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
|
||||||
try {
|
try {
|
||||||
setupJWEHeader(jweStr);
|
setupJWEHeader(jweStr);
|
||||||
return getProcessedJWE(JWERegistry.getAlgProvider(header.getAlgorithm()), JWERegistry.getEncProvider(header.getEncryptionAlgorithm()));
|
return verifyAndDecodeJwe();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new JWEException(e);
|
throw new JWEException(e);
|
||||||
}
|
}
|
||||||
|
@ -221,6 +230,14 @@ public class JWE {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JWE verifyAndDecodeJwe() throws JWEException {
|
||||||
|
try {
|
||||||
|
return getProcessedJWE(JWERegistry.getAlgProvider(header.getAlgorithm()), JWERegistry.getEncProvider(header.getEncryptionAlgorithm()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JWEException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String encryptUTF8(String password, String saltString, String payload) {
|
public static String encryptUTF8(String password, String saltString, String payload) {
|
||||||
byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
|
byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
|
||||||
return encrypt(password, saltString, bytes);
|
return encrypt(password, saltString, bytes);
|
||||||
|
|
|
@ -18,18 +18,19 @@
|
||||||
package org.keycloak.jose.jwe;
|
package org.keycloak.jose.jwe;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.keycloak.jose.JOSEHeader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class JWEHeader implements Serializable {
|
public class JWEHeader implements JOSEHeader {
|
||||||
|
|
||||||
@JsonProperty("alg")
|
@JsonProperty("alg")
|
||||||
private String algorithm;
|
private String algorithm;
|
||||||
|
@ -70,6 +71,12 @@ public class JWEHeader implements Serializable {
|
||||||
return algorithm;
|
return algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public String getRawAlgorithm() {
|
||||||
|
return getAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
public String getEncryptionAlgorithm() {
|
public String getEncryptionAlgorithm() {
|
||||||
return encryptionAlgorithm;
|
return encryptionAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,10 @@ import java.util.Map;
|
||||||
import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
|
import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
|
||||||
import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
|
import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
|
||||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||||
|
import org.keycloak.jose.jwe.alg.RsaKeyEncryption256JWEAlgorithmProvider;
|
||||||
|
import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
|
||||||
import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
|
import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
|
||||||
|
import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
|
||||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,8 +48,11 @@ class JWERegistry {
|
||||||
// Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
|
// Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
|
||||||
ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
|
ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
|
||||||
ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
|
ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
|
||||||
|
ALG_PROVIDERS.put(JWEConstants.RSA_OAEP, new RsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
|
||||||
|
ALG_PROVIDERS.put(JWEConstants.RSA_OAEP_256, new RsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
|
||||||
|
|
||||||
|
|
||||||
|
ENC_PROVIDERS.put(JWEConstants.A256GCM, new AesGcmJWEEncryptionProvider(JWEConstants.A256GCM));
|
||||||
ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
|
ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
|
||||||
ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
|
ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
|
||||||
ENC_PROVIDERS.put(JWEConstants.A256CBC_HS512, new AesCbcHmacShaEncryptionProvider.Aes256CbcHmacSha512Provider());
|
ENC_PROVIDERS.put(JWEConstants.A256CBC_HS512, new AesCbcHmacShaEncryptionProvider.Aes256CbcHmacSha512Provider());
|
||||||
|
|
|
@ -68,14 +68,18 @@ public class JWKBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWK rsa(Key key) {
|
public JWK rsa(Key key) {
|
||||||
return rsa(key, (List<X509Certificate>) null);
|
return rsa(key, null, KeyUse.SIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWK rsa(Key key, X509Certificate certificate) {
|
public JWK rsa(Key key, X509Certificate certificate) {
|
||||||
return rsa(key, Collections.singletonList(certificate));
|
return rsa(key, Collections.singletonList(certificate), KeyUse.SIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWK rsa(Key key, List<X509Certificate> certificates) {
|
public JWK rsa(Key key, List<X509Certificate> certificates) {
|
||||||
|
return rsa(key, certificates, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWK rsa(Key key, List<X509Certificate> certificates, KeyUse keyUse) {
|
||||||
RSAPublicKey rsaKey = (RSAPublicKey) key;
|
RSAPublicKey rsaKey = (RSAPublicKey) key;
|
||||||
|
|
||||||
RSAPublicJWK k = new RSAPublicJWK();
|
RSAPublicJWK k = new RSAPublicJWK();
|
||||||
|
@ -84,7 +88,7 @@ public class JWKBuilder {
|
||||||
k.setKeyId(kid);
|
k.setKeyId(kid);
|
||||||
k.setKeyType(KeyType.RSA);
|
k.setKeyType(KeyType.RSA);
|
||||||
k.setAlgorithm(algorithm);
|
k.setAlgorithm(algorithm);
|
||||||
k.setPublicKeyUse(DEFAULT_PUBLIC_KEY_USE);
|
k.setPublicKeyUse(keyUse == null ? KeyUse.SIG.getSpecName() : keyUse.getSpecName());
|
||||||
k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
|
k.setModulus(Base64Url.encode(toIntegerBytes(rsaKey.getModulus())));
|
||||||
k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));
|
k.setPublicExponent(Base64Url.encode(toIntegerBytes(rsaKey.getPublicExponent())));
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,21 @@
|
||||||
|
|
||||||
package org.keycloak.jose.jws;
|
package org.keycloak.jose.jws;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.keycloak.jose.JOSEHeader;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class JWSHeader implements Serializable {
|
public class JWSHeader implements JOSEHeader {
|
||||||
@JsonProperty("alg")
|
@JsonProperty("alg")
|
||||||
private Algorithm algorithm;
|
private Algorithm algorithm;
|
||||||
|
|
||||||
|
@ -62,6 +63,12 @@ public class JWSHeader implements Serializable {
|
||||||
return algorithm;
|
return algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public String getRawAlgorithm() {
|
||||||
|
return getAlgorithm().name();
|
||||||
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.jose.jws;
|
package org.keycloak.jose.jws;
|
||||||
|
|
||||||
import org.keycloak.common.util.Base64Url;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -27,7 +28,7 @@ import java.nio.charset.StandardCharsets;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JWSInput {
|
public class JWSInput implements JOSE {
|
||||||
String wireString;
|
String wireString;
|
||||||
String encodedHeader;
|
String encodedHeader;
|
||||||
String encodedContent;
|
String encodedContent;
|
||||||
|
@ -90,13 +91,6 @@ public class JWSInput {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean verify(String key) {
|
|
||||||
if (header.getAlgorithm().getProvider() == null) {
|
|
||||||
throw new RuntimeException("signing algorithm not supported");
|
|
||||||
}
|
|
||||||
return header.getAlgorithm().getProvider().verify(this, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T readJsonContent(Class<T> type) throws JWSInputException {
|
public <T> T readJsonContent(Class<T> type) throws JWSInputException {
|
||||||
try {
|
try {
|
||||||
return JsonSerialization.readValue(content, type);
|
return JsonSerialization.readValue(content, type);
|
||||||
|
|
|
@ -79,6 +79,12 @@ public class OIDCConfigurationRepresentation {
|
||||||
@JsonProperty("request_object_signing_alg_values_supported")
|
@JsonProperty("request_object_signing_alg_values_supported")
|
||||||
private List<String> requestObjectSigningAlgValuesSupported;
|
private List<String> requestObjectSigningAlgValuesSupported;
|
||||||
|
|
||||||
|
@JsonProperty("request_object_encryption_alg_values_supported")
|
||||||
|
private List<String> requestObjectEncryptionAlgValuesSupported;
|
||||||
|
|
||||||
|
@JsonProperty("request_object_encryption_enc_values_supported")
|
||||||
|
private List<String> requestObjectEncryptionEncValuesSupported;
|
||||||
|
|
||||||
@JsonProperty("response_modes_supported")
|
@JsonProperty("response_modes_supported")
|
||||||
private List<String> responseModesSupported;
|
private List<String> responseModesSupported;
|
||||||
|
|
||||||
|
@ -299,6 +305,22 @@ public class OIDCConfigurationRepresentation {
|
||||||
this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported;
|
this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getRequestObjectEncryptionAlgValuesSupported() {
|
||||||
|
return requestObjectEncryptionAlgValuesSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestObjectEncryptionAlgValuesSupported(List<String> requestObjectEncryptionAlgValuesSupported) {
|
||||||
|
this.requestObjectEncryptionAlgValuesSupported = requestObjectEncryptionAlgValuesSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRequestObjectEncryptionEncValuesSupported() {
|
||||||
|
return requestObjectEncryptionEncValuesSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestObjectEncryptionEncValuesSupported(List<String> requestObjectEncryptionEncValuesSupported) {
|
||||||
|
this.requestObjectEncryptionEncValuesSupported = requestObjectEncryptionEncValuesSupported;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getResponseModesSupported() {
|
public List<String> getResponseModesSupported() {
|
||||||
return responseModesSupported;
|
return responseModesSupported;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ package org.keycloak.representations.idm;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
|
@ -58,6 +60,7 @@ public class KeysMetadataRepresentation {
|
||||||
|
|
||||||
private String publicKey;
|
private String publicKey;
|
||||||
private String certificate;
|
private String certificate;
|
||||||
|
private KeyUse use;
|
||||||
|
|
||||||
public String getProviderId() {
|
public String getProviderId() {
|
||||||
return providerId;
|
return providerId;
|
||||||
|
@ -122,5 +125,13 @@ public class KeysMetadataRepresentation {
|
||||||
public void setCertificate(String certificate) {
|
public void setCertificate(String certificate) {
|
||||||
this.certificate = certificate;
|
this.certificate = certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyUse getUse() {
|
||||||
|
return use;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUse(KeyUse use) {
|
||||||
|
this.use = use;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.models.utils;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.crypto.Algorithm;
|
import org.keycloak.crypto.Algorithm;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
@ -32,23 +33,29 @@ public class DefaultKeyProviders {
|
||||||
|
|
||||||
public static void createProviders(RealmModel realm) {
|
public static void createProviders(RealmModel realm) {
|
||||||
if (!hasProvider(realm, "rsa-generated")) {
|
if (!hasProvider(realm, "rsa-generated")) {
|
||||||
ComponentModel generated = new ComponentModel();
|
createRsaKeyProvider("rsa-generated", KeyUse.SIG, realm);
|
||||||
generated.setName("rsa-generated");
|
createRsaKeyProvider("rsa-enc-generated", KeyUse.ENC, realm);
|
||||||
generated.setParentId(realm.getId());
|
|
||||||
generated.setProviderId("rsa-generated");
|
|
||||||
generated.setProviderType(KeyProvider.class.getName());
|
|
||||||
|
|
||||||
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
|
||||||
config.putSingle("priority", "100");
|
|
||||||
generated.setConfig(config);
|
|
||||||
|
|
||||||
realm.addComponentModel(generated);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSecretProvider(realm);
|
createSecretProvider(realm);
|
||||||
createAesProvider(realm);
|
createAesProvider(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void createRsaKeyProvider(String name, KeyUse keyUse, RealmModel realm) {
|
||||||
|
ComponentModel generated = new ComponentModel();
|
||||||
|
generated.setName(name);
|
||||||
|
generated.setParentId(realm.getId());
|
||||||
|
generated.setProviderId("rsa-generated");
|
||||||
|
generated.setProviderType(KeyProvider.class.getName());
|
||||||
|
|
||||||
|
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
|
||||||
|
config.putSingle("priority", "100");
|
||||||
|
config.putSingle("keyUse", keyUse.getSpecName());
|
||||||
|
generated.setConfig(config);
|
||||||
|
|
||||||
|
realm.addComponentModel(generated);
|
||||||
|
}
|
||||||
|
|
||||||
public static void createSecretProvider(RealmModel realm) {
|
public static void createSecretProvider(RealmModel realm) {
|
||||||
if (hasProvider(realm, "hmac-generated")) return;
|
if (hasProvider(realm, "hmac-generated")) return;
|
||||||
ComponentModel generated = new ComponentModel();
|
ComponentModel generated = new ComponentModel();
|
||||||
|
|
|
@ -16,12 +16,24 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.keycloak.Token;
|
import org.keycloak.Token;
|
||||||
import org.keycloak.TokenCategory;
|
import org.keycloak.TokenCategory;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
import org.keycloak.representations.LogoutToken;
|
import org.keycloak.representations.LogoutToken;
|
||||||
|
|
||||||
public interface TokenManager {
|
public interface TokenManager {
|
||||||
|
|
||||||
|
BiConsumer<JOSE, ClientModel> DEFAULT_VALIDATOR = (jwt, client) -> {
|
||||||
|
String rawAlgorithm = jwt.getHeader().getRawAlgorithm();
|
||||||
|
|
||||||
|
if (rawAlgorithm.equalsIgnoreCase(Algorithm.none.name())) {
|
||||||
|
throw new RuntimeException("Algorithm none not supported");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the supplied token
|
* Encodes the supplied token
|
||||||
*
|
*
|
||||||
|
@ -42,7 +54,21 @@ public interface TokenManager {
|
||||||
|
|
||||||
String signatureAlgorithm(TokenCategory category);
|
String signatureAlgorithm(TokenCategory category);
|
||||||
|
|
||||||
<T> T decodeClientJWT(String token, ClientModel client, Class<T> clazz);
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* @param client
|
||||||
|
* @param clazz
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default <T> T decodeClientJWT(String token, ClientModel client, Class<T> clazz) {
|
||||||
|
return decodeClientJWT(token, client, DEFAULT_VALIDATOR, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
<T> T decodeClientJWT(String token, ClientModel client, BiConsumer<JOSE, ClientModel> jwtValidator,
|
||||||
|
Class<T> clazz);
|
||||||
|
|
||||||
String encodeAndEncrypt(Token token);
|
String encodeAndEncrypt(Token token);
|
||||||
String cekManagementAlgorithm(TokenCategory category);
|
String cekManagementAlgorithm(TokenCategory category);
|
||||||
|
|
|
@ -27,6 +27,9 @@ import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.crypto.SignatureProvider;
|
import org.keycloak.crypto.SignatureProvider;
|
||||||
import org.keycloak.crypto.SignatureSignerContext;
|
import org.keycloak.crypto.SignatureSignerContext;
|
||||||
|
import org.keycloak.jose.JOSEParser;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
|
import org.keycloak.jose.jwe.JWE;
|
||||||
import org.keycloak.jose.jwe.JWEException;
|
import org.keycloak.jose.jwe.JWEException;
|
||||||
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
|
||||||
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
|
||||||
|
@ -47,8 +50,17 @@ import org.keycloak.representations.LogoutToken;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.util.TokenUtil;
|
import org.keycloak.util.TokenUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class DefaultTokenManager implements TokenManager {
|
public class DefaultTokenManager implements TokenManager {
|
||||||
|
|
||||||
|
@ -103,17 +115,69 @@ public class DefaultTokenManager implements TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T decodeClientJWT(String token, ClientModel client, Class<T> clazz) {
|
public <T> T decodeClientJWT(String jwt, ClientModel client, BiConsumer<JOSE, ClientModel> jwtValidator, Class<T> clazz) {
|
||||||
if (token == null) {
|
if (jwt == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JOSE joseToken = JOSEParser.parse(jwt);
|
||||||
|
|
||||||
|
jwtValidator.accept(joseToken, client);
|
||||||
|
|
||||||
|
if (joseToken instanceof JWE) {
|
||||||
|
try {
|
||||||
|
Optional<KeyWrapper> activeKey;
|
||||||
|
String kid = joseToken.getHeader().getKeyId();
|
||||||
|
Stream<KeyWrapper> keys = session.keys().getKeysStream(session.getContext().getRealm());
|
||||||
|
|
||||||
|
if (kid == null) {
|
||||||
|
activeKey = keys.filter(k -> KeyUse.ENC.equals(k.getUse()) && k.getPublicKey() != null)
|
||||||
|
.sorted(Comparator.comparingLong(KeyWrapper::getProviderPriority).reversed())
|
||||||
|
.findFirst();
|
||||||
|
} else {
|
||||||
|
activeKey = keys
|
||||||
|
.filter(k -> KeyUse.ENC.equals(k.getUse()) && k.getKid().equals(kid)).findAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
JWE jwe = JWE.class.cast(joseToken);
|
||||||
|
Key privateKey = activeKey.map(KeyWrapper::getPrivateKey)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Could not find private key for decrypting token"));
|
||||||
|
|
||||||
|
jwe.getKeyStorage().setDecryptionKey(privateKey);
|
||||||
|
|
||||||
|
byte[] content = jwe.verifyAndDecodeJwe().getContent();
|
||||||
|
|
||||||
|
try {
|
||||||
|
JOSE jws = JOSEParser.parse(new String(content));
|
||||||
|
|
||||||
|
if (jws instanceof JWSInput) {
|
||||||
|
jwtValidator.accept(jws, client);
|
||||||
|
return verifyJWS(client, clazz, (JWSInput) jws);
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
// try to decrypt content as is
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerialization.readValue(content, clazz);
|
||||||
|
} catch (IOException cause) {
|
||||||
|
throw new RuntimeException("Failed to deserialize JWT", cause);
|
||||||
|
} catch (JWEException cause) {
|
||||||
|
throw new RuntimeException("Failed to decrypt JWT", cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifyJWS(client, clazz, (JWSInput) joseToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T verifyJWS(ClientModel client, Class<T> clazz, JWSInput jws) {
|
||||||
try {
|
try {
|
||||||
JWSInput jws = new JWSInput(token);
|
|
||||||
|
|
||||||
String signatureAlgorithm = jws.getHeader().getAlgorithm().name();
|
String signatureAlgorithm = jws.getHeader().getAlgorithm().name();
|
||||||
|
|
||||||
ClientSignatureVerifierProvider signatureProvider = session.getProvider(ClientSignatureVerifierProvider.class, signatureAlgorithm);
|
ClientSignatureVerifierProvider signatureProvider = session.getProvider(ClientSignatureVerifierProvider.class, signatureAlgorithm);
|
||||||
|
|
||||||
if (signatureProvider == null) {
|
if (signatureProvider == null) {
|
||||||
|
if (jws.getHeader().getAlgorithm().equals(org.keycloak.jose.jws.Algorithm.none)) {
|
||||||
|
return jws.readJsonContent(clazz);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,18 +61,19 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
|
||||||
return Stream.of(key);
|
return Stream.of(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate) {
|
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate, KeyUse keyUse) {
|
||||||
return createKeyWrapper(keyPair, certificate, Collections.emptyList());
|
return createKeyWrapper(keyPair, certificate, Collections.emptyList(), keyUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate, List<X509Certificate> certificateChain) {
|
protected KeyWrapper createKeyWrapper(KeyPair keyPair, X509Certificate certificate, List<X509Certificate> certificateChain,
|
||||||
|
KeyUse keyUse) {
|
||||||
KeyWrapper key = new KeyWrapper();
|
KeyWrapper key = new KeyWrapper();
|
||||||
|
|
||||||
key.setProviderId(model.getId());
|
key.setProviderId(model.getId());
|
||||||
key.setProviderPriority(model.get("priority", 0l));
|
key.setProviderPriority(model.get("priority", 0l));
|
||||||
|
|
||||||
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
|
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
|
||||||
key.setUse(KeyUse.SIG);
|
key.setUse(keyUse == null ? KeyUse.SIG : keyUse);
|
||||||
key.setType(KeyType.RSA);
|
key.setType(KeyType.RSA);
|
||||||
key.setAlgorithm(algorithm);
|
key.setAlgorithm(algorithm);
|
||||||
key.setStatus(status);
|
key.setStatus(status);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.keys;
|
package org.keycloak.keys;
|
||||||
|
|
||||||
import org.keycloak.crypto.Algorithm;
|
import org.keycloak.crypto.Algorithm;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import static org.keycloak.provider.ProviderConfigProperty.*;
|
import static org.keycloak.provider.ProviderConfigProperty.*;
|
||||||
|
@ -45,6 +46,10 @@ public interface Attributes {
|
||||||
String KEY_SIZE_KEY = "keySize";
|
String KEY_SIZE_KEY = "keySize";
|
||||||
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Key size", "Size for the generated keys", LIST_TYPE, "2048", "1024", "2048", "4096");
|
ProviderConfigProperty KEY_SIZE_PROPERTY = new ProviderConfigProperty(KEY_SIZE_KEY, "Key size", "Size for the generated keys", LIST_TYPE, "2048", "1024", "2048", "4096");
|
||||||
|
|
||||||
|
String KEY_USE = "keyUse";
|
||||||
|
ProviderConfigProperty KEY_USE_PROPERTY = new ProviderConfigProperty(KEY_USE, "Key use", "Whether the key should be used for signing or encryption.", LIST_TYPE,
|
||||||
|
KeyUse.SIG.getSpecName(), KeyUse.SIG.getSpecName(), KeyUse.ENC.getSpecName());
|
||||||
|
|
||||||
String KID_KEY = "kid";
|
String KID_KEY = "kid";
|
||||||
|
|
||||||
String SECRET_KEY = "secret";
|
String SECRET_KEY = "secret";
|
||||||
|
|
|
@ -50,6 +50,7 @@ public class GeneratedRsaKeyProviderFactory extends AbstractRsaKeyProviderFactor
|
||||||
|
|
||||||
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
|
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractRsaKeyProviderFactory.configurationBuilder()
|
||||||
.property(Attributes.KEY_SIZE_PROPERTY)
|
.property(Attributes.KEY_SIZE_PROPERTY)
|
||||||
|
.property(Attributes.KEY_USE_PROPERTY)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.keys;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
@ -48,7 +49,9 @@ public class ImportedRsaKeyProvider extends AbstractRsaKeyProvider {
|
||||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||||
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
|
X509Certificate certificate = PemUtils.decodeCertificate(certificatePem);
|
||||||
|
|
||||||
return createKeyWrapper(keyPair, certificate);
|
KeyUse keyUse = KeyUse.valueOf(model.get(Attributes.KEY_USE, KeyUse.SIG.name()).toUpperCase());
|
||||||
|
|
||||||
|
return createKeyWrapper(keyPair, certificate, keyUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,10 @@ package org.keycloak.keys;
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -76,7 +76,9 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
|
||||||
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
return createKeyWrapper(keyPair, certificate, loadCertificateChain(keyStore, keyAlias));
|
KeyUse keyUse = KeyUse.valueOf(model.get(Attributes.KEY_USE, KeyUse.SIG.getSpecName()).toUpperCase());
|
||||||
|
|
||||||
|
return createKeyWrapper(keyPair, certificate, loadCertificateChain(keyStore, keyAlias), keyUse);
|
||||||
} catch (KeyStoreException kse) {
|
} catch (KeyStoreException kse) {
|
||||||
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
|
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
|
|
|
@ -227,14 +227,14 @@ public class OIDCLoginProtocolService {
|
||||||
checkSsl();
|
checkSsl();
|
||||||
|
|
||||||
JWK[] jwks = session.keys().getKeysStream(realm)
|
JWK[] jwks = session.keys().getKeysStream(realm)
|
||||||
.filter(k -> k.getStatus().isEnabled() && Objects.equals(k.getUse(), KeyUse.SIG) && k.getPublicKey() != null)
|
.filter(k -> k.getStatus().isEnabled() && k.getPublicKey() != null)
|
||||||
.map(k -> {
|
.map(k -> {
|
||||||
JWKBuilder b = JWKBuilder.create().kid(k.getKid()).algorithm(k.getAlgorithm());
|
JWKBuilder b = JWKBuilder.create().kid(k.getKid()).algorithm(k.getAlgorithm());
|
||||||
List<X509Certificate> certificates = Optional.ofNullable(k.getCertificateChain())
|
List<X509Certificate> certificates = Optional.ofNullable(k.getCertificateChain())
|
||||||
.filter(certs -> !certs.isEmpty())
|
.filter(certs -> !certs.isEmpty())
|
||||||
.orElseGet(() -> Collections.singletonList(k.getCertificate()));
|
.orElseGet(() -> Collections.singletonList(k.getCertificate()));
|
||||||
if (k.getType().equals(KeyType.RSA)) {
|
if (k.getType().equals(KeyType.RSA)) {
|
||||||
return b.rsa(k.getPublicKey(), certificates);
|
return b.rsa(k.getPublicKey(), certificates, k.getUse());
|
||||||
} else if (k.getType().equals(KeyType.EC)) {
|
} else if (k.getType().equals(KeyType.EC)) {
|
||||||
return b.ec(k.getPublicKey());
|
return b.ec(k.getPublicKey());
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||||
config.setIdTokenEncryptionEncValuesSupported(getSupportedEncryptionEnc(false));
|
config.setIdTokenEncryptionEncValuesSupported(getSupportedEncryptionEnc(false));
|
||||||
config.setUserInfoSigningAlgValuesSupported(getSupportedSigningAlgorithms(true));
|
config.setUserInfoSigningAlgValuesSupported(getSupportedSigningAlgorithms(true));
|
||||||
config.setRequestObjectSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(true));
|
config.setRequestObjectSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(true));
|
||||||
|
config.setRequestObjectEncryptionAlgValuesSupported(getSupportedEncryptionAlgorithms());
|
||||||
|
config.setRequestObjectEncryptionEncValuesSupported(getSupportedContentEncryptionAlgorithms());
|
||||||
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
|
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
|
||||||
config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED);
|
config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED);
|
||||||
config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED);
|
config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED);
|
||||||
|
@ -232,6 +234,14 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||||
return getSupportedAlgorithms(ClientSignatureVerifierProvider.class, includeNone);
|
return getSupportedAlgorithms(ClientSignatureVerifierProvider.class, includeNone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> getSupportedContentEncryptionAlgorithms() {
|
||||||
|
return getSupportedAlgorithms(ContentEncryptionProvider.class, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getSupportedEncryptionAlgorithms() {
|
||||||
|
return getSupportedAlgorithms(CekManagementProvider.class, false);
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> getSupportedBackchannelAuthenticationRequestSigningAlgorithms() {
|
private List<String> getSupportedBackchannelAuthenticationRequestSigningAlgorithms() {
|
||||||
return getSupportedAsymmetricAlgorithms();
|
return getSupportedAsymmetricAlgorithms();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,16 @@
|
||||||
package org.keycloak.protocol.oidc.endpoints.request;
|
package org.keycloak.protocol.oidc.endpoints.request;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.keycloak.jose.JOSEHeader;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
import org.keycloak.jose.jws.JWSHeader;
|
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the parameters from OIDC "request" object
|
* Parse the parameters from OIDC "request" object
|
||||||
|
@ -36,30 +35,33 @@ import org.keycloak.util.JsonSerialization;
|
||||||
*/
|
*/
|
||||||
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
|
|
||||||
private final JsonNode requestParams;
|
private static void validateAlgorithm(JOSE jwt, ClientModel clientModel) {
|
||||||
|
if (jwt instanceof JWSInput) {
|
||||||
|
JOSEHeader header = jwt.getHeader();
|
||||||
|
String headerAlgorithm = header.getRawAlgorithm();
|
||||||
|
|
||||||
public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception {
|
if (headerAlgorithm == null) {
|
||||||
JWSInput input = new JWSInput(requestObject);
|
throw new RuntimeException("Request object signed algorithm not specified");
|
||||||
JWSHeader header = input.getHeader();
|
}
|
||||||
Algorithm headerAlgorithm = header.getAlgorithm();
|
|
||||||
|
|
||||||
Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg();
|
Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(clientModel)
|
||||||
|
.getRequestObjectSignatureAlg();
|
||||||
|
|
||||||
if (headerAlgorithm == null) {
|
if (requestedSignatureAlgorithm != null && !requestedSignatureAlgorithm.name().equals(headerAlgorithm)) {
|
||||||
throw new RuntimeException("Request object signed algorithm not specified");
|
throw new RuntimeException("Request object signed with different algorithm than client requested algorithm");
|
||||||
}
|
|
||||||
if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != headerAlgorithm) {
|
|
||||||
throw new RuntimeException("Request object signed with different algorithm than client requested algorithm");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.getAlgorithm() == Algorithm.none) {
|
|
||||||
this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class);
|
|
||||||
} else {
|
|
||||||
this.requestParams = session.tokens().decodeClientJWT(requestObject, client, JsonNode.class);
|
|
||||||
if (this.requestParams == null) {
|
|
||||||
throw new RuntimeException("Failed to verify signature on 'request' object");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final JsonNode requestParams;
|
||||||
|
|
||||||
|
public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) {
|
||||||
|
this.requestParams = session.tokens().decodeClientJWT(requestObject, client, AuthzEndpointRequestObjectParser::validateAlgorithm, JsonNode.class);
|
||||||
|
|
||||||
|
if (this.requestParams == null) {
|
||||||
|
throw new RuntimeException("Failed to verify signature on 'request' object");
|
||||||
|
}
|
||||||
|
|
||||||
session.setAttribute(AuthzEndpointRequestParser.AUTHZ_REQUEST_OBJECT, requestParams);
|
session.setAttribute(AuthzEndpointRequestParser.AUTHZ_REQUEST_OBJECT, requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +89,4 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
|
||||||
requestParams.fieldNames().forEachRemaining(keys::add);
|
requestParams.fieldNames().forEachRemaining(keys::add);
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TypedHashMap extends HashMap<String, Object> {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,13 @@
|
||||||
package org.keycloak.protocol.oidc.grants.ciba.endpoints.request;
|
package org.keycloak.protocol.oidc.grants.ciba.endpoints.request;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.crypto.SignatureProvider;
|
import org.keycloak.crypto.SignatureProvider;
|
||||||
|
import org.keycloak.jose.JOSE;
|
||||||
|
import org.keycloak.jose.JOSEParser;
|
||||||
|
import org.keycloak.jose.jwe.JWE;
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
import org.keycloak.jose.jws.JWSHeader;
|
import org.keycloak.jose.jws.JWSHeader;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
|
@ -41,7 +43,13 @@ class BackchannelAuthenticationEndpointSignedRequestParser extends BackchannelAu
|
||||||
private final JsonNode requestParams;
|
private final JsonNode requestParams;
|
||||||
|
|
||||||
public BackchannelAuthenticationEndpointSignedRequestParser(KeycloakSession session, String signedAuthReq, ClientModel client, CibaConfig config) throws Exception {
|
public BackchannelAuthenticationEndpointSignedRequestParser(KeycloakSession session, String signedAuthReq, ClientModel client, CibaConfig config) throws Exception {
|
||||||
JWSInput input = new JWSInput(signedAuthReq);
|
JOSE jwt = JOSEParser.parse(signedAuthReq);
|
||||||
|
|
||||||
|
if (jwt instanceof JWE) {
|
||||||
|
throw new RuntimeException("Encrypted request object is not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
JWSInput input = (JWSInput) jwt;
|
||||||
JWSHeader header = input.getHeader();
|
JWSHeader header = input.getHeader();
|
||||||
Algorithm headerAlgorithm = header.getAlgorithm();
|
Algorithm headerAlgorithm = header.getAlgorithm();
|
||||||
|
|
||||||
|
@ -96,7 +104,4 @@ class BackchannelAuthenticationEndpointSignedRequestParser extends BackchannelAu
|
||||||
requestParams.fieldNames().forEachRemaining(keys::add);
|
requestParams.fieldNames().forEachRemaining(keys::add);
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TypedHashMap extends HashMap<String, Object> {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ public class KeyResource {
|
||||||
r.setAlgorithm(key.getAlgorithm());
|
r.setAlgorithm(key.getAlgorithm());
|
||||||
r.setPublicKey(key.getPublicKey() != null ? PemUtils.encodeKey(key.getPublicKey()) : null);
|
r.setPublicKey(key.getPublicKey() != null ? PemUtils.encodeKey(key.getPublicKey()) : null);
|
||||||
r.setCertificate(key.getCertificate() != null ? PemUtils.encodeCertificate(key.getCertificate()) : null);
|
r.setCertificate(key.getCertificate() != null ? PemUtils.encodeCertificate(key.getCertificate()) : null);
|
||||||
|
r.setUse(key.getUse());
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.RoleResource;
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.crypto.Algorithm;
|
import org.keycloak.crypto.Algorithm;
|
||||||
|
import org.keycloak.crypto.KeyStatus;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
@ -268,11 +270,10 @@ public class ApiUtil {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveKey(RealmResource realm) {
|
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveSigningKey(RealmResource realm) {
|
||||||
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
|
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
|
||||||
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
|
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
|
||||||
if (rep.getKid().equals(activeKid)) {
|
if (rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.SIG.equals(rep.getUse())) {
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.testsuite.util;
|
package org.keycloak.testsuite.util;
|
||||||
|
|
||||||
import org.keycloak.common.util.BouncyIntegration;
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
|
import org.keycloak.crypto.KeyStatus;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||||
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
|
@ -41,10 +43,18 @@ public class KeyUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveKey(KeysMetadataRepresentation keys, String algorithm) {
|
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveSigningKey(KeysMetadataRepresentation keys, String algorithm) {
|
||||||
String kid = keys.getActive().get(algorithm);
|
|
||||||
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||||
if (k.getKid().equals(kid)) {
|
if (k.getAlgorithm().equals(algorithm) && KeyStatus.valueOf(k.getStatus()).isActive() && KeyUse.SIG.equals(k.getUse())) {
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Active key not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveEncKey(KeysMetadataRepresentation keys, String algorithm) {
|
||||||
|
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
|
||||||
|
if (k.getAlgorithm().equals(algorithm) && KeyStatus.valueOf(k.getStatus()).isActive() && KeyUse.ENC.equals(k.getUse())) {
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
@ -168,7 +167,7 @@ public class InstallationTest extends AbstractClientTest {
|
||||||
|
|
||||||
private void assertOidcInstallationConfig(String config) {
|
private void assertOidcInstallationConfig(String config) {
|
||||||
assertThat(config, containsString("test"));
|
assertThat(config, containsString("test"));
|
||||||
assertThat(config, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getPublicKey())));
|
assertThat(config, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getPublicKey())));
|
||||||
assertThat(config, containsString(authServerUrl()));
|
assertThat(config, containsString(authServerUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +181,7 @@ public class InstallationTest extends AbstractClientTest {
|
||||||
String xml = samlClient.getInstallationProvider("keycloak-saml");
|
String xml = samlClient.getInstallationProvider("keycloak-saml");
|
||||||
assertThat(xml, containsString("<keycloak-saml-adapter>"));
|
assertThat(xml, containsString("<keycloak-saml-adapter>"));
|
||||||
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
|
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
|
||||||
assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
|
assertThat(xml, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
|
||||||
assertThat(xml, containsString(samlUrl()));
|
assertThat(xml, containsString(samlUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +190,7 @@ public class InstallationTest extends AbstractClientTest {
|
||||||
String cli = samlClient.getInstallationProvider("keycloak-saml-subsystem-cli");
|
String cli = samlClient.getInstallationProvider("keycloak-saml-subsystem-cli");
|
||||||
assertThat(cli, containsString("/subsystem=keycloak-saml/secure-deployment=YOUR-WAR.war/"));
|
assertThat(cli, containsString("/subsystem=keycloak-saml/secure-deployment=YOUR-WAR.war/"));
|
||||||
assertThat(cli, containsString("SPECIFY YOUR entityID!"));
|
assertThat(cli, containsString("SPECIFY YOUR entityID!"));
|
||||||
assertThat(cli, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
|
assertThat(cli, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
|
||||||
assertThat(cli, containsString(samlUrl()));
|
assertThat(cli, containsString(samlUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +208,7 @@ public class InstallationTest extends AbstractClientTest {
|
||||||
String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
|
String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
|
||||||
assertThat(xml, containsString("<secure-deployment"));
|
assertThat(xml, containsString("<secure-deployment"));
|
||||||
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
|
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
|
||||||
assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
|
assertThat(xml, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
|
||||||
assertThat(xml, containsString(samlUrl()));
|
assertThat(xml, containsString(samlUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ public abstract class AbstractGroupTest extends AbstractKeycloakTest {
|
||||||
String accessToken = tokenResponse.getAccessToken();
|
String accessToken = tokenResponse.getAccessToken();
|
||||||
String refreshToken = tokenResponse.getRefreshToken();
|
String refreshToken = tokenResponse.getRefreshToken();
|
||||||
|
|
||||||
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveKey(adminClient.realm("test")).getPublicKey());
|
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
|
||||||
|
|
||||||
AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, publicKey, getAuthServerContextRoot() + "/auth/realms/test");
|
AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, publicKey, getAuthServerContextRoot() + "/auth/realms/test");
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
cfg.setValidateSignature(true);
|
cfg.setValidateSignature(true);
|
||||||
cfg.setUseJwksUrl(false);
|
cfg.setUseJwksUrl(false);
|
||||||
|
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveKey(providerRealm());
|
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm());
|
||||||
cfg.setPublicKeySignatureVerifier(key.getPublicKey());
|
cfg.setPublicKeySignatureVerifier(key.getPublicKey());
|
||||||
updateIdentityProvider(idpRep);
|
updateIdentityProvider(idpRep);
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
cfg.setValidateSignature(true);
|
cfg.setValidateSignature(true);
|
||||||
cfg.setUseJwksUrl(false);
|
cfg.setUseJwksUrl(false);
|
||||||
|
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveKey(providerRealm());
|
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm());
|
||||||
String pemData = key.getPublicKey();
|
String pemData = key.getPublicKey();
|
||||||
cfg.setPublicKeySignatureVerifier(pemData);
|
cfg.setPublicKeySignatureVerifier(pemData);
|
||||||
String expectedKeyId = KeyUtils.createKeyId(PemUtils.decodePublicKey(pemData));
|
String expectedKeyId = KeyUtils.createKeyId(PemUtils.decodePublicKey(pemData));
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class KcOidcBrokerPrivateKeyJwtTest extends AbstractBrokerTest {
|
||||||
public List<ClientRepresentation> createProviderClients() {
|
public List<ClientRepresentation> createProviderClients() {
|
||||||
List<ClientRepresentation> clientsRepList = super.createProviderClients();
|
List<ClientRepresentation> clientsRepList = super.createProviderClients();
|
||||||
log.info("Update provider clients to accept JWT authentication");
|
log.info("Update provider clients to accept JWT authentication");
|
||||||
KeyMetadataRepresentation keyRep = KeyUtils.getActiveKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256);
|
KeyMetadataRepresentation keyRep = KeyUtils.getActiveSigningKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256);
|
||||||
for (ClientRepresentation client: clientsRepList) {
|
for (ClientRepresentation client: clientsRepList) {
|
||||||
client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||||
if (client.getAttributes() == null) {
|
if (client.getAttributes() == null) {
|
||||||
|
|
|
@ -64,10 +64,10 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
|
||||||
private static final String PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALOOiAmD0SJJq/HYhApsk+fXAoU1iBIl2AWN0+ji5WaxfKH1Qs2xHqFDpoa7R4o8cbikqKi1j+JzTrd6yDbUDQUCAwEAAQ==";
|
private static final String PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALOOiAmD0SJJq/HYhApsk+fXAoU1iBIl2AWN0+ji5WaxfKH1Qs2xHqFDpoa7R4o8cbikqKi1j+JzTrd6yDbUDQUCAwEAAQ==";
|
||||||
|
|
||||||
public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception {
|
public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception {
|
||||||
String providerCert = KeyUtils.getActiveKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(providerCert, Matchers.notNullValue());
|
Assert.assertThat(providerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
||||||
|
@ -271,7 +271,7 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
|
||||||
public List<ClientRepresentation> createProviderClients() {
|
public List<ClientRepresentation> createProviderClients() {
|
||||||
List<ClientRepresentation> clientRepresentationList = super.createProviderClients();
|
List<ClientRepresentation> clientRepresentationList = super.createProviderClients();
|
||||||
|
|
||||||
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
for (ClientRepresentation client : clientRepresentationList) {
|
for (ClientRepresentation client : clientRepresentationList) {
|
||||||
|
@ -298,7 +298,7 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
|
||||||
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
|
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
|
||||||
IdentityProviderRepresentation result = super.setUpIdentityProvider(syncMode);
|
IdentityProviderRepresentation result = super.setUpIdentityProvider(syncMode);
|
||||||
|
|
||||||
String providerCert = KeyUtils.getActiveKey(adminClient.realm(providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(providerCert, Matchers.notNullValue());
|
Assert.assertThat(providerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
Map<String, String> config = result.getConfig();
|
Map<String, String> config = result.getConfig();
|
||||||
|
@ -452,10 +452,10 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
|
||||||
public void testSignatureDataWhenWantsRequestsSigned() throws Exception {
|
public void testSignatureDataWhenWantsRequestsSigned() throws Exception {
|
||||||
// Verifies that an AuthnRequest contains the KeyInfo/X509Data element when
|
// Verifies that an AuthnRequest contains the KeyInfo/X509Data element when
|
||||||
// client AuthnRequest signature is requested
|
// client AuthnRequest signature is requested
|
||||||
String providerCert = KeyUtils.getActiveKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(providerCert, Matchers.notNullValue());
|
Assert.assertThat(providerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
String consumerCert = KeyUtils.getActiveKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
|
||||||
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
Assert.assertThat(consumerCert, Matchers.notNullValue());
|
||||||
|
|
||||||
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package org.keycloak.testsuite.oidc;
|
package org.keycloak.testsuite.oidc;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -28,28 +30,44 @@ import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
import org.keycloak.admin.client.resource.ProtocolMappersResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.jose.jwe.JWE;
|
||||||
|
import org.keycloak.jose.jwe.JWEConstants;
|
||||||
|
import org.keycloak.jose.jwe.JWEException;
|
||||||
|
import org.keycloak.jose.jwe.JWEHeader;
|
||||||
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
|
import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jws.Algorithm;
|
import org.keycloak.jose.jws.Algorithm;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.keys.Attributes;
|
||||||
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.mappers.ClaimsParameterTokenMapper;
|
import org.keycloak.protocol.oidc.mappers.ClaimsParameterTokenMapper;
|
||||||
|
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||||
import org.keycloak.representations.IDToken;
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.representations.UserInfo;
|
import org.keycloak.representations.UserInfo;
|
||||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
|
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.util.CertificateInfoHelper;
|
import org.keycloak.services.util.CertificateInfoHelper;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
@ -68,14 +86,17 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||||
import org.keycloak.testsuite.util.ClientManager;
|
import org.keycloak.testsuite.util.ClientManager;
|
||||||
|
import org.keycloak.testsuite.util.KeyUtils;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.UserInfoClientUtil;
|
import org.keycloak.testsuite.util.UserInfoClientUtil;
|
||||||
|
import org.keycloak.util.JWKSUtils;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
import javax.ws.rs.client.Client;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -84,6 +105,8 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.keycloak.jose.jwe.JWEConstants.RSA_OAEP;
|
||||||
|
import static org.keycloak.jose.jwe.JWEConstants.RSA_OAEP_256;
|
||||||
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
|
import static org.keycloak.testsuite.admin.ApiUtil.findClientResourceByClientId;
|
||||||
|
|
||||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
|
||||||
|
@ -120,6 +143,40 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
||||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterAbstractKeycloakTestRealmImport() {
|
||||||
|
String realmId = testRealm().toRepresentation().getId();
|
||||||
|
ComponentRepresentation keys = new ComponentRepresentation();
|
||||||
|
|
||||||
|
keys.setName("enc-generated");
|
||||||
|
keys.setProviderType(KeyProvider.class.getName());
|
||||||
|
keys.setProviderId("rsa-generated");
|
||||||
|
keys.setParentId(realmId);
|
||||||
|
keys.setConfig(new MultivaluedHashMap<>());
|
||||||
|
keys.getConfig().putSingle("priority", "150");
|
||||||
|
keys.getConfig().putSingle(Attributes.KEY_USE, KeyUse.ENC.getSpecName());
|
||||||
|
keys.getConfig().putSingle("algorithm", org.keycloak.crypto.Algorithm.RS256);
|
||||||
|
|
||||||
|
try (Response response = testRealm().components().add(keys)) {
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = new ComponentRepresentation();
|
||||||
|
|
||||||
|
keys.setName("enc-generated");
|
||||||
|
keys.setProviderType(KeyProvider.class.getName());
|
||||||
|
keys.setProviderId("rsa-generated");
|
||||||
|
keys.setParentId(realmId);
|
||||||
|
keys.setConfig(new MultivaluedHashMap<>());
|
||||||
|
keys.getConfig().putSingle("priority", "200");
|
||||||
|
keys.getConfig().putSingle(Attributes.KEY_USE, KeyUse.ENC.getSpecName());
|
||||||
|
keys.getConfig().putSingle("algorithm", org.keycloak.crypto.Algorithm.PS256);
|
||||||
|
|
||||||
|
try (Response response = testRealm().components().add(keys)) {
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void clientConfiguration() {
|
public void clientConfiguration() {
|
||||||
ClientManager.realm(adminClient.realm("test")).clientId("test-app")
|
ClientManager.realm(adminClient.realm("test")).clientId("test-app")
|
||||||
|
@ -1236,4 +1293,130 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
|
||||||
findClientResourceByClientId(adminClient.realm("test"), "test-app").addDefaultClientScope(clientScopeId);
|
findClientResourceByClientId(adminClient.realm("test"), "test-app").addDefaultClientScope(clientScopeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignedRequestObject() throws IOException {
|
||||||
|
oauth = oauth.request(createAndSignRequestObject());
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSignedAndEncryptedRequestObject() throws IOException, JWEException {
|
||||||
|
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||||
|
OIDCConfigurationRepresentation representation = SimpleHttp
|
||||||
|
.doGet(getAuthServerRoot().toString() + "realms/" + oauth.getRealm() + "/.well-known/openid-configuration",
|
||||||
|
httpClient).asJson(OIDCConfigurationRepresentation.class);
|
||||||
|
String jwksUri = representation.getJwksUri();
|
||||||
|
JSONWebKeySet jsonWebKeySet = SimpleHttp.doGet(jwksUri, httpClient).asJson(JSONWebKeySet.class);
|
||||||
|
Map<String, PublicKey> keysForUse = JWKSUtils.getKeysForUse(jsonWebKeySet, JWK.Use.ENCRYPTION);
|
||||||
|
String keyId = null;
|
||||||
|
|
||||||
|
if (keyId == null) {
|
||||||
|
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.getActiveEncKey(testRealm().keys().getKeyMetadata(),
|
||||||
|
org.keycloak.crypto.Algorithm.PS256);
|
||||||
|
keyId = encKey.getKid();
|
||||||
|
}
|
||||||
|
|
||||||
|
PublicKey decryptionKEK = keysForUse.get(keyId);
|
||||||
|
JWE jwe = new JWE()
|
||||||
|
.header(new JWEHeader(RSA_OAEP_256, JWEConstants.A256GCM, null))
|
||||||
|
.content(createAndSignRequestObject().getBytes());
|
||||||
|
|
||||||
|
jwe.getKeyStorage()
|
||||||
|
.setEncryptionKey(decryptionKEK);
|
||||||
|
|
||||||
|
oauth = oauth.request(jwe.encodeJwe());
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRealmPublicKeyEncryptedRequestObjectUsingRSA_OAEP_256WithA256GCM() throws Exception {
|
||||||
|
assertRequestObjectEncryption(new JWEHeader(RSA_OAEP_256, JWEConstants.A256GCM, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRealmPublicKeyEncryptedRequestObjectUsingRSA_OAEPWithA128CBC_HS256() throws Exception {
|
||||||
|
assertRequestObjectEncryption(new JWEHeader(RSA_OAEP, JWEConstants.A128CBC_HS256, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRealmPublicKeyEncryptedRequestObjectUsingKid() throws Exception {
|
||||||
|
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.getActiveEncKey(testRealm().keys().getKeyMetadata(),
|
||||||
|
org.keycloak.crypto.Algorithm.RS256);
|
||||||
|
JWEHeader jweHeader = new JWEHeader(RSA_OAEP, JWEConstants.A128CBC_HS256, null, encKey.getKid());
|
||||||
|
assertRequestObjectEncryption(jweHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createAndSignRequestObject() throws IOException {
|
||||||
|
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");
|
||||||
|
|
||||||
|
byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
|
||||||
|
String encodedRequestObject = Base64Url.encode(contentBytes);
|
||||||
|
TestOIDCEndpointsApplicationResource client = testingClient.testApp().oidcClientEndpoints();
|
||||||
|
|
||||||
|
// use and set jwks_url
|
||||||
|
ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm(oauth.getRealm()), oauth.getClientId());
|
||||||
|
ClientRepresentation clientRep = clientResource.toRepresentation();
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
|
||||||
|
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(TestApplicationResourceUrls.clientJwksUri());
|
||||||
|
clientResource.update(clientRep);
|
||||||
|
client.generateKeys(org.keycloak.crypto.Algorithm.RS256);
|
||||||
|
client.registerOIDCRequest(encodedRequestObject, org.keycloak.crypto.Algorithm.RS256);
|
||||||
|
|
||||||
|
String oidcRequest = client.getOIDCRequest();
|
||||||
|
return oidcRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertRequestObjectEncryption(JWEHeader jweHeader) throws Exception {
|
||||||
|
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");
|
||||||
|
|
||||||
|
byte[] contentBytes = JsonSerialization.writeValueAsBytes(requestObject);
|
||||||
|
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||||
|
OIDCConfigurationRepresentation representation = SimpleHttp
|
||||||
|
.doGet(getAuthServerRoot().toString() + "realms/" + oauth.getRealm() + "/.well-known/openid-configuration",
|
||||||
|
httpClient).asJson(OIDCConfigurationRepresentation.class);
|
||||||
|
String jwksUri = representation.getJwksUri();
|
||||||
|
JSONWebKeySet jsonWebKeySet = SimpleHttp.doGet(jwksUri, httpClient).asJson(JSONWebKeySet.class);
|
||||||
|
Map<String, PublicKey> keysForUse = JWKSUtils.getKeysForUse(jsonWebKeySet, JWK.Use.ENCRYPTION);
|
||||||
|
String keyId = jweHeader.getKeyId();
|
||||||
|
|
||||||
|
if (keyId == null) {
|
||||||
|
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.getActiveEncKey(testRealm().keys().getKeyMetadata(),
|
||||||
|
org.keycloak.crypto.Algorithm.PS256);
|
||||||
|
keyId = encKey.getKid();
|
||||||
|
}
|
||||||
|
|
||||||
|
PublicKey decryptionKEK = keysForUse.get(keyId);
|
||||||
|
JWE jwe = new JWE()
|
||||||
|
.header(jweHeader)
|
||||||
|
.content(contentBytes);
|
||||||
|
|
||||||
|
jwe.getKeyStorage()
|
||||||
|
.setEncryptionKey(decryptionKEK);
|
||||||
|
|
||||||
|
oauth = oauth.request(jwe.encodeJwe());
|
||||||
|
oauth.doLogin("test-user@localhost", "password");
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,10 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
|
||||||
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||||
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
|
||||||
|
|
||||||
|
// request object encryption algorithms
|
||||||
|
Assert.assertNames(oidcConfig.getRequestObjectEncryptionAlgValuesSupported(), JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256, JWEConstants.RSA1_5);
|
||||||
|
Assert.assertNames(oidcConfig.getRequestObjectEncryptionEncValuesSupported(), JWEConstants.A256GCM, JWEConstants.A192GCM, JWEConstants.A128GCM, JWEConstants.A128CBC_HS256, JWEConstants.A192CBC_HS384, JWEConstants.A256CBC_HS512);
|
||||||
|
|
||||||
// Encryption algorithms
|
// Encryption algorithms
|
||||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
|
||||||
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
|
||||||
|
|
|
@ -265,7 +265,7 @@ public class UserInfoTest extends AbstractKeycloakTest {
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
|
|
||||||
// Check signature and content
|
// Check signature and content
|
||||||
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveKey(adminClient.realm("test")).getPublicKey());
|
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
|
||||||
|
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);
|
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);
|
||||||
|
|
|
@ -1937,3 +1937,4 @@ user.profile.attribute.validation.add.validator=Add Validator
|
||||||
user.profile.attribute.validation.add.validator.tooltip=Select a validator to enforce specific constraints to the attribute value.
|
user.profile.attribute.validation.add.validator.tooltip=Select a validator to enforce specific constraints to the attribute value.
|
||||||
user.profile.attribute.validation.no.validators=No validators.
|
user.profile.attribute.validation.no.validators=No validators.
|
||||||
user.profile.attribute.annotation=Annotation
|
user.profile.attribute.annotation=Annotation
|
||||||
|
use=Use
|
|
@ -28,7 +28,7 @@
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="kc-table-actions" colspan="7">
|
<th class="kc-table-actions" colspan="8">
|
||||||
<div class="form-inline">
|
<div class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
<th>{{:: 'algorithm' | translate}}</th>
|
<th>{{:: 'algorithm' | translate}}</th>
|
||||||
<th>{{:: 'type' | translate}}</th>
|
<th>{{:: 'type' | translate}}</th>
|
||||||
<th>{{:: 'kid' | translate}}</th>
|
<th>{{:: 'kid' | translate}}</th>
|
||||||
|
<th>{{:: 'use' | translate}}</th>
|
||||||
<th>{{:: 'priority' | translate}}</th>
|
<th>{{:: 'priority' | translate}}</th>
|
||||||
<th>{{:: 'provider' | translate}}</th>
|
<th>{{:: 'provider' | translate}}</th>
|
||||||
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
<th colspan="2">{{:: 'publicKeys' | translate}}</th>
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
<td>{{key.algorithm}}</td>
|
<td>{{key.algorithm}}</td>
|
||||||
<td>{{key.type}}</td>
|
<td>{{key.type}}</td>
|
||||||
<td>{{key.kid}}</td>
|
<td>{{key.kid}}</td>
|
||||||
|
<td>{{key.use}}</td>
|
||||||
<td>{{key.providerPriority}}</td>
|
<td>{{key.providerPriority}}</td>
|
||||||
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
|
<td><a href="#/realms/{{realm.realm}}/keys/providers/{{key.provider.providerId}}/{{key.provider.id}}">{{key.provider.name}}</a></td>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue