diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java
index 4693f62512..848f8d431c 100644
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java
+++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java
@@ -163,7 +163,7 @@ public class KcinitDriver {
JWE jwe = new JWE();
final SecretKey aesSecret = new SecretKeySpec(aesKey, "AES");
jwe.getKeyStorage()
- .setEncryptionKey(aesSecret);
+ .setDecryptionKey(aesSecret);
return jwe;
}
diff --git a/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureSignerContext.java b/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureSignerContext.java
index 0eae6eca64..f2331c810f 100644
--- a/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureSignerContext.java
+++ b/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureSignerContext.java
@@ -46,7 +46,7 @@ public class AsymmetricSignatureSignerContext implements SignatureSignerContext
public byte[] sign(byte[] data) throws SignatureException {
try {
Signature signature = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
- signature.initSign((PrivateKey) key.getSignKey());
+ signature.initSign((PrivateKey) key.getPrivateKey());
signature.update(data);
return signature.sign();
} catch (Exception e) {
diff --git a/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureVerifierContext.java b/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureVerifierContext.java
index fd93e1c690..8bd5e472c0 100644
--- a/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureVerifierContext.java
+++ b/core/src/main/java/org/keycloak/crypto/AsymmetricSignatureVerifierContext.java
@@ -43,7 +43,7 @@ public class AsymmetricSignatureVerifierContext implements SignatureVerifierCont
public boolean verify(byte[] data, byte[] signature) throws VerificationException {
try {
Signature verifier = Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(key.getAlgorithm()));
- verifier.initVerify((PublicKey) key.getVerifyKey());
+ verifier.initVerify((PublicKey) key.getPublicKey());
verifier.update(data);
return verifier.verify(signature);
} catch (Exception e) {
diff --git a/core/src/main/java/org/keycloak/crypto/KeyUse.java b/core/src/main/java/org/keycloak/crypto/KeyUse.java
index c5256f7e73..ddf6d06c52 100644
--- a/core/src/main/java/org/keycloak/crypto/KeyUse.java
+++ b/core/src/main/java/org/keycloak/crypto/KeyUse.java
@@ -18,7 +18,17 @@ package org.keycloak.crypto;
public enum KeyUse {
- SIG,
- ENC
+ SIG("sig"),
+ ENC("enc");
+
+ private String specName;
+
+ KeyUse(String specName) {
+ this.specName = specName;
+ }
+
+ public String getSpecName() {
+ return specName;
+ }
}
diff --git a/core/src/main/java/org/keycloak/crypto/KeyWrapper.java b/core/src/main/java/org/keycloak/crypto/KeyWrapper.java
index 85d8151e09..130e8c3677 100644
--- a/core/src/main/java/org/keycloak/crypto/KeyWrapper.java
+++ b/core/src/main/java/org/keycloak/crypto/KeyWrapper.java
@@ -30,8 +30,8 @@ public class KeyWrapper {
private KeyUse use;
private KeyStatus status;
private SecretKey secretKey;
- private Key signKey;
- private Key verifyKey;
+ private Key publicKey;
+ private Key privateKey;
private X509Certificate certificate;
public String getProviderId() {
@@ -98,20 +98,20 @@ public class KeyWrapper {
this.secretKey = secretKey;
}
- public Key getSignKey() {
- return signKey;
+ public Key getPrivateKey() {
+ return privateKey;
}
- public void setSignKey(Key signKey) {
- this.signKey = signKey;
+ public void setPrivateKey(Key privateKey) {
+ this.privateKey = privateKey;
}
- public Key getVerifyKey() {
- return verifyKey;
+ public Key getPublicKey() {
+ return publicKey;
}
- public void setVerifyKey(Key verifyKey) {
- this.verifyKey = verifyKey;
+ public void setPublicKey(Key publicKey) {
+ this.publicKey = publicKey;
}
public X509Certificate getCertificate() {
@@ -121,4 +121,5 @@ public class KeyWrapper {
public void setCertificate(X509Certificate certificate) {
this.certificate = certificate;
}
+
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWE.java b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
index 8d954eaba8..947e881749 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWE.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
@@ -19,8 +19,6 @@ package org.keycloak.jose.jwe;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import org.keycloak.common.util.Base64;
@@ -121,6 +119,15 @@ public class JWE {
public String encodeJwe() throws JWEException {
+ try {
+ if (header == null) throw new IllegalStateException("Header must be set");
+ return encodeJwe(JWERegistry.getAlgProvider(header.getAlgorithm()), JWERegistry.getEncProvider(header.getEncryptionAlgorithm()));
+ } catch (Exception e) {
+ throw new JWEException(e);
+ }
+ }
+
+ public String encodeJwe(JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws JWEException {
try {
if (header == null) {
throw new IllegalStateException("Header must be set");
@@ -129,12 +136,10 @@ public class JWE {
throw new IllegalStateException("Content must be set");
}
- JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
if (algorithmProvider == null) {
throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
}
- JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
if (encryptionProvider == null) {
throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
}
@@ -153,7 +158,6 @@ public class JWE {
}
}
-
private String getEncodedJweString() {
StringBuilder builder = new StringBuilder();
builder.append(base64Header).append(".")
@@ -165,39 +169,53 @@ public class JWE {
return builder.toString();
}
+ private void setupJWEHeader(String jweStr) throws IllegalStateException {
+ String[] parts = jweStr.split("\\.");
+ if (parts.length != 5) {
+ throw new IllegalStateException("Not a JWE String");
+ }
+
+ this.base64Header = parts[0];
+ this.base64Cek = parts[1];
+ this.initializationVector = Base64Url.decode(parts[2]);
+ this.encryptedContent = Base64Url.decode(parts[3]);
+ this.authenticationTag = Base64Url.decode(parts[4]);
+
+ this.header = getHeader();
+ }
+
+ private JWE getProcessedJWE(JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws Exception {
+ if (algorithmProvider == null) {
+ throw new IllegalArgumentException("No provider for alg ");
+ }
+
+ if (encryptionProvider == null) {
+ throw new IllegalArgumentException("No provider for enc ");
+ }
+
+ keyStorage.setEncryptionProvider(encryptionProvider);
+
+ byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getDecryptionKey());
+ keyStorage.setCEKBytes(decodedCek);
+
+ encryptionProvider.verifyAndDecodeJwe(this);
+
+ return this;
+ }
public JWE verifyAndDecodeJwe(String jweStr) throws JWEException {
try {
- String[] parts = jweStr.split("\\.");
- if (parts.length != 5) {
- throw new IllegalStateException("Not a JWE String");
- }
+ setupJWEHeader(jweStr);
+ return getProcessedJWE(JWERegistry.getAlgProvider(header.getAlgorithm()), JWERegistry.getEncProvider(header.getEncryptionAlgorithm()));
+ } catch (Exception e) {
+ throw new JWEException(e);
+ }
+ }
- this.base64Header = parts[0];
- this.base64Cek = parts[1];
- this.initializationVector = Base64Url.decode(parts[2]);
- this.encryptedContent = Base64Url.decode(parts[3]);
- this.authenticationTag = Base64Url.decode(parts[4]);
-
- this.header = getHeader();
- JWEAlgorithmProvider algorithmProvider = JWERegistry.getAlgProvider(header.getAlgorithm());
- if (algorithmProvider == null) {
- throw new IllegalArgumentException("No provider for alg '" + header.getAlgorithm() + "'");
- }
-
- JWEEncryptionProvider encryptionProvider = JWERegistry.getEncProvider(header.getEncryptionAlgorithm());
- if (encryptionProvider == null) {
- throw new IllegalArgumentException("No provider for enc '" + header.getAlgorithm() + "'");
- }
-
- keyStorage.setEncryptionProvider(encryptionProvider);
-
- byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getEncryptionKey());
- keyStorage.setCEKBytes(decodedCek);
-
- encryptionProvider.verifyAndDecodeJwe(this);
-
- return this;
+ public JWE verifyAndDecodeJwe(String jweStr, JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws JWEException {
+ try {
+ setupJWEHeader(jweStr);
+ return getProcessedJWE(algorithmProvider, encryptionProvider);
} catch (Exception e) {
throw new JWEException(e);
}
@@ -247,7 +265,7 @@ public class JWE {
JWE jwe = new JWE();
jwe.getKeyStorage()
- .setEncryptionKey(aesKey);
+ .setDecryptionKey(aesKey);
jwe.verifyAndDecodeJwe(encodedJwe);
return jwe.getContent();
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
index d81141dbd1..9da14624e7 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
@@ -24,9 +24,13 @@ public class JWEConstants {
public static final String DIR = "dir";
public static final String A128KW = "A128KW";
+ public static final String RSA1_5 = "RSA1_5";
+ public static final String RSA_OAEP = "RSA-OAEP";
public static final String A128CBC_HS256 = "A128CBC-HS256";
public static final String A192CBC_HS384 = "A192CBC-HS384";
public static final String A256CBC_HS512 = "A256CBC-HS512";
-
+ public static final String A128GCM = "A128GCM";
+ public static final String A192GCM = "A192GCM";
+ public static final String A256GCM = "A256GCM";
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
index 30b5150a46..5ca24b5c38 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
@@ -59,6 +59,13 @@ public class JWEHeader implements Serializable {
this.compressionAlgorithm = compressionAlgorithm;
}
+ public JWEHeader(String algorithm, String encryptionAlgorithm, String compressionAlgorithm, String keyId) {
+ this.algorithm = algorithm;
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ this.compressionAlgorithm = compressionAlgorithm;
+ this.keyId = keyId;
+ }
+
public String getAlgorithm() {
return algorithm;
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
index 9ce042ce03..4d5f2efb44 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
@@ -29,6 +29,7 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class JWEKeyStorage {
private Key encryptionKey;
+ private Key decryptionKey;
private byte[] cekBytes;
@@ -46,6 +47,14 @@ public class JWEKeyStorage {
return this;
}
+ public Key getDecryptionKey() {
+ return decryptionKey;
+ }
+
+ public JWEKeyStorage setDecryptionKey(Key decryptionKey) {
+ this.decryptionKey = decryptionKey;
+ return this;
+ }
public void setCEKBytes(byte[] cekBytes) {
this.cekBytes = cekBytes;
@@ -100,4 +109,5 @@ public class JWEKeyStorage {
ENCRYPTION,
SIGNATURE
}
+
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/KeyEncryptionJWEAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/KeyEncryptionJWEAlgorithmProvider.java
new file mode 100644
index 0000000000..778a77d47e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/KeyEncryptionJWEAlgorithmProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jose.jwe.alg;
+
+import java.security.Key;
+
+import javax.crypto.Cipher;
+
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+public abstract class KeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmProvider {
+
+ @Override
+ public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
+ Cipher cipher = getCipherProvider();
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ return cipher.doFinal(encodedCek);
+ }
+
+ @Override
+ public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
+ Cipher cipher = getCipherProvider();
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] cekBytes = keyStorage.getCekBytes();
+ return cipher.doFinal(cekBytes);
+ }
+
+ protected abstract Cipher getCipherProvider() throws Exception;
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/RsaKeyEncryptionJWEAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/RsaKeyEncryptionJWEAlgorithmProvider.java
new file mode 100644
index 0000000000..c6909027dc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/RsaKeyEncryptionJWEAlgorithmProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jose.jwe.alg;
+
+import javax.crypto.Cipher;
+
+public class RsaKeyEncryptionJWEAlgorithmProvider extends KeyEncryptionJWEAlgorithmProvider {
+
+ private final String jcaAlgorithmName;
+
+ public RsaKeyEncryptionJWEAlgorithmProvider(String jcaAlgorithmName) {
+ this.jcaAlgorithmName = jcaAlgorithmName;
+ }
+
+ @Override
+ protected Cipher getCipherProvider() throws Exception {
+ return Cipher.getInstance(jcaAlgorithmName);
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaJWEEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaJWEEncryptionProvider.java
new file mode 100644
index 0000000000..3edb08e142
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaJWEEncryptionProvider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jose.jwe.enc;
+
+import org.keycloak.jose.jwe.JWEConstants;
+
+public class AesCbcHmacShaJWEEncryptionProvider extends AesCbcHmacShaEncryptionProvider {
+
+ private final int expectedCEKLength;
+ private final int expectedAesKeyLength;
+ private final String hmacShaAlgorithm;
+ private final int authenticationTagLength;
+
+
+ public AesCbcHmacShaJWEEncryptionProvider(String jwaAlgorithmName) {
+ if (JWEConstants.A128CBC_HS256.equals(jwaAlgorithmName)) {
+ expectedCEKLength = 32;
+ expectedAesKeyLength = 16;
+ hmacShaAlgorithm = "HMACSHA256";
+ authenticationTagLength = 16;
+ } else {
+ expectedCEKLength = 0;
+ expectedAesKeyLength = 0;
+ hmacShaAlgorithm = null;
+ authenticationTagLength = 0;
+ }
+ }
+
+ @Override
+ public int getExpectedCEKLength() {
+ return expectedCEKLength;
+ }
+
+ @Override
+ protected int getExpectedAesKeyLength() {
+ return expectedAesKeyLength;
+ }
+
+ @Override
+ protected String getHmacShaAlgorithm() {
+ return hmacShaAlgorithm;
+ }
+
+ @Override
+ protected int getAuthenticationTagLength() {
+ return authenticationTagLength;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java
new file mode 100644
index 0000000000..2af9119836
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmEncryptionProvider.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jose.jwe.enc;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.JWEUtils;
+
+public abstract class AesGcmEncryptionProvider implements JWEEncryptionProvider {
+
+ // 96 bits of IV is required
+ // Authentication Tag size must be 128 bits
+ // https://tools.ietf.org/html/rfc7518#section-5.3
+ private static final int AUTH_TAG_SIZE_BYTE = 16;
+ private static final int IV_SIZE_BYTE = 12;
+
+ @Override
+ public void encodeJwe(JWE jwe) throws Exception {
+
+ byte[] contentBytes = jwe.getContent();
+
+ // IV must be nonce (number used once)
+ byte[] initializationVector = JWEUtils.generateSecret(IV_SIZE_BYTE);
+
+ Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+ if (aesKey == null) {
+ throw new IllegalArgumentException("AES CEK key not present");
+ }
+
+ int expectedAesKeyLength = getExpectedAesKeyLength();
+ if (expectedAesKeyLength != aesKey.getEncoded().length) {
+ throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
+ }
+
+ // https://tools.ietf.org/html/rfc7516#appendix-A.1.5
+ byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+
+ byte[] cipherBytes = encryptBytes(contentBytes, initializationVector, aesKey, aad);
+ byte[] authenticationTag = getAuthenticationTag(cipherBytes);
+ byte[] encryptedContent = getEncryptedContent(cipherBytes);
+ jwe.setEncryptedContentInfo(initializationVector, encryptedContent, authenticationTag);
+
+ }
+
+ @Override
+ public void verifyAndDecodeJwe(JWE jwe) throws Exception {
+ Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+ if (aesKey == null) {
+ throw new IllegalArgumentException("AES CEK key not present");
+ }
+
+ int expectedAesKeyLength = getExpectedAesKeyLength();
+ if (expectedAesKeyLength != aesKey.getEncoded().length) {
+ throw new IllegalStateException("Length of aes key should be " + expectedAesKeyLength +", but was " + aesKey.getEncoded().length);
+ }
+
+ // https://tools.ietf.org/html/rfc7516#appendix-A.1.5
+ byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+ byte[] decryptedTargetContent = getAeadDecryptedTargetContent(jwe);
+ byte[] contentBytes = decryptBytes(decryptedTargetContent, jwe.getInitializationVector(), aesKey, aad);
+
+ jwe.content(contentBytes);
+ }
+
+ private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey, byte[] aad) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
+ GCMParameterSpec gcmParams = new GCMParameterSpec(AUTH_TAG_SIZE_BYTE * 8, ivBytes);
+ cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmParams);
+ cipher.updateAAD(aad);
+ byte[] cipherText = new byte[cipher.getOutputSize(contentBytes.length)];
+ cipher.doFinal(contentBytes, 0, contentBytes.length, cipherText);
+ return cipherText;
+ }
+
+ private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey, byte[] aad) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
+ GCMParameterSpec gcmParams = new GCMParameterSpec(AUTH_TAG_SIZE_BYTE * 8, ivBytes);
+ cipher.init(Cipher.DECRYPT_MODE, aesKey, gcmParams);
+ cipher.updateAAD(aad);
+ return cipher.doFinal(encryptedBytes);
+ }
+
+ private byte[] getAuthenticationTag(byte[] cipherBytes) {
+ // AES GCM cipher text consists of a cipher text an authentication tag.
+ // The authentication tag be encoded as an individual term in JWE.
+ // So extract it from the AES GCM cipher text.
+ // https://tools.ietf.org/html/rfc5116#section-5.1
+ byte[] authenticationTag = new byte[AUTH_TAG_SIZE_BYTE];
+ System.arraycopy(cipherBytes, cipherBytes.length - authenticationTag.length, authenticationTag, 0, authenticationTag.length);
+ return authenticationTag;
+ }
+
+ private byte[] getEncryptedContent(byte[] cipherBytes) throws NoSuchAlgorithmException, InvalidKeyException {
+ // AES GCM cipher text consists of a cipher text an authentication tag.
+ // The cipher text be encoded as an individual term in JWE.
+ // So extract it from the AES GCM cipher text.
+ // https://tools.ietf.org/html/rfc5116#section-5.1
+ byte[] encryptedContent = new byte[cipherBytes.length - AUTH_TAG_SIZE_BYTE];
+ System.arraycopy(cipherBytes, 0, encryptedContent, 0, encryptedContent.length);
+ return encryptedContent;
+ }
+
+ private byte[] getAeadDecryptedTargetContent(JWE jwe) {
+ // In order to decrypt, need to construct AES GCM cipher text from JWE cipher text and authentication tag
+ byte[] encryptedContent = jwe.getEncryptedContent();
+ byte[] authTag = jwe.getAuthenticationTag();
+ byte[] decryptedTargetContent = new byte[authTag.length + encryptedContent.length];
+ System.arraycopy(encryptedContent, 0, decryptedTargetContent, 0, encryptedContent.length);
+ System.arraycopy(authTag, 0, decryptedTargetContent, encryptedContent.length, authTag.length);
+ return decryptedTargetContent;
+ }
+
+ @Override
+ public byte[] serializeCEK(JWEKeyStorage keyStorage) {
+ Key aesKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+ if (aesKey == null) {
+ throw new IllegalArgumentException("AES CEK key not present");
+ }
+ byte[] aesBytes = aesKey.getEncoded();
+ return aesBytes;
+ }
+
+ @Override
+ public void deserializeCEK(JWEKeyStorage keyStorage) {
+ byte[] cekBytes = keyStorage.getCekBytes();
+ SecretKeySpec aesKey = new SecretKeySpec(cekBytes, "AES");
+ keyStorage.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION);
+ }
+
+ protected abstract int getExpectedAesKeyLength();
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmJWEEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmJWEEncryptionProvider.java
new file mode 100644
index 0000000000..ba5cea79f4
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesGcmJWEEncryptionProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.jose.jwe.enc;
+
+import org.keycloak.jose.jwe.JWEConstants;
+
+public class AesGcmJWEEncryptionProvider extends AesGcmEncryptionProvider {
+
+ private final int expectedAesKeyLength;
+ private final int expectedCEKLength;
+
+
+ public AesGcmJWEEncryptionProvider(String jwaAlgorithmName) {
+ if (JWEConstants.A128GCM.equals(jwaAlgorithmName)) {
+ expectedAesKeyLength = 16;
+ expectedCEKLength = 16;
+ } else {
+ expectedAesKeyLength = 0;
+ expectedCEKLength = 0;
+ }
+ }
+
+ @Override
+ protected int getExpectedAesKeyLength() {
+ return expectedAesKeyLength;
+ }
+
+ @Override
+ public int getExpectedCEKLength() {
+ return expectedCEKLength;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
index 2230a785f9..5298d932ba 100644
--- a/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKBuilder.java
@@ -22,6 +22,7 @@ import org.keycloak.common.util.KeyUtils;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
+import org.keycloak.crypto.KeyUse;
import java.math.BigInteger;
import java.security.Key;
@@ -64,7 +65,7 @@ public class JWKBuilder {
}
public JWK rsa(Key key) {
- return rsa(key, null);
+ return rsa(key, (X509Certificate)null);
}
public JWK rsa(Key key, X509Certificate certificate) {
@@ -87,6 +88,14 @@ public class JWKBuilder {
return k;
}
+ public JWK rsa(Key key, KeyUse keyUse) {
+ JWK k = rsa(key);
+ String keyUseString = keyUse == null ? DEFAULT_PUBLIC_KEY_USE : keyUse.getSpecName();
+ if (KeyUse.ENC == keyUse) keyUseString = "enc";
+ k.setPublicKeyUse(keyUseString);
+ return k;
+ }
+
public JWK ec(Key key) {
ECPublicKey ecKey = (ECPublicKey) key;
diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java
index 1745239129..729192edfa 100644
--- a/core/src/main/java/org/keycloak/util/JWKSUtils.java
+++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java
@@ -55,7 +55,7 @@ public class JWKSUtils {
keyWrapper.setAlgorithm(jwk.getAlgorithm());
keyWrapper.setType(jwk.getKeyType());
keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse()));
- keyWrapper.setVerifyKey(parser.toPublicKey());
+ keyWrapper.setPublicKey(parser.toPublicKey());
result.put(keyWrapper.getKid(), keyWrapper);
}
}
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 86eb10b15d..d2bae90098 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -23,6 +23,8 @@ import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.JsonWebToken;
@@ -174,4 +176,34 @@ public class TokenUtil {
}
}
+ public static String jweKeyEncryptionEncode(Key encryptionKEK, byte[] contentBytes, String algAlgorithm, String encAlgorithm, String kid, JWEAlgorithmProvider jweAlgorithmProvider, JWEEncryptionProvider jweEncryptionProvider) throws JWEException {
+ JWEHeader jweHeader = new JWEHeader(algAlgorithm, encAlgorithm, null, kid);
+ return jweKeyEncryptionEncode(encryptionKEK, contentBytes, jweHeader, jweAlgorithmProvider, jweEncryptionProvider);
+ }
+
+ private static String jweKeyEncryptionEncode(Key encryptionKEK, byte[] contentBytes, JWEHeader jweHeader, JWEAlgorithmProvider jweAlgorithmProvider, JWEEncryptionProvider jweEncryptionProvider) throws JWEException {
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(contentBytes);
+ jwe.getKeyStorage()
+ .setEncryptionKey(encryptionKEK);
+ String encodedContent = jwe.encodeJwe(jweAlgorithmProvider, jweEncryptionProvider);
+ return encodedContent;
+ }
+
+ public static byte[] jweKeyEncryptionVerifyAndDecode(Key decryptionKEK, String encodedContent) throws JWEException {
+ JWE jwe = new JWE();
+ jwe.getKeyStorage()
+ .setDecryptionKey(decryptionKEK);
+ jwe.verifyAndDecodeJwe(encodedContent);
+ return jwe.getContent();
+ }
+
+ public static byte[] jweKeyEncryptionVerifyAndDecode(Key decryptionKEK, String encodedContent, JWEAlgorithmProvider algorithmProvider, JWEEncryptionProvider encryptionProvider) throws JWEException {
+ JWE jwe = new JWE();
+ jwe.getKeyStorage()
+ .setDecryptionKey(decryptionKEK);
+ jwe.verifyAndDecodeJwe(encodedContent, algorithmProvider, encryptionProvider);
+ return jwe.getContent();
+ }
}
diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java
index cc179bf988..61cffda469 100644
--- a/core/src/test/java/org/keycloak/jose/JWETest.java
+++ b/core/src/test/java/org/keycloak/jose/JWETest.java
@@ -19,18 +19,26 @@ package org.keycloak.jose;
import java.io.UnsupportedEncodingException;
import java.security.Key;
-import java.security.spec.KeySpec;
+import java.security.KeyPair;
+import javax.crypto.Cipher;
import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.KeyUtils;
import org.keycloak.jose.jwe.*;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.KeyEncryptionJWEAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
+import org.keycloak.jose.jwe.enc.AesCbcHmacShaJWEEncryptionProvider;
+import org.keycloak.jose.jwe.enc.AesGcmEncryptionProvider;
+import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
/**
* @author Marek Posolda
@@ -151,7 +159,7 @@ public class JWETest {
jwe = new JWE();
jwe.getKeyStorage()
- .setEncryptionKey(aesKey);
+ .setDecryptionKey(aesKey);
jwe.verifyAndDecodeJwe(encodedContent);
@@ -223,7 +231,7 @@ public class JWETest {
JWE jwe = new JWE();
jwe.getKeyStorage()
- .setEncryptionKey(aesKeySpec);
+ .setDecryptionKey(aesKeySpec);
jwe.verifyAndDecodeJwe(externalJwe);
@@ -233,4 +241,102 @@ public class JWETest {
}
+ @Test
+ public void testRSA1_5_A128GCM() throws Exception {
+ testKeyEncryption_ContentEncryptionAesGcm(JWEConstants.RSA1_5, JWEConstants.A128GCM);
+ }
+
+ @Test
+ public void testRSAOAEP_A128GCM() throws Exception {
+ testKeyEncryption_ContentEncryptionAesGcm(JWEConstants.RSA_OAEP, JWEConstants.A128GCM);
+ }
+
+ @Test
+ public void testRSA1_5_A128CBCHS256() throws Exception {
+ testKeyEncryption_ContentEncryptionAesHmacSha(JWEConstants.RSA1_5, JWEConstants.A128CBC_HS256);
+ }
+
+ @Test
+ public void testRSAOAEP_A128CBCHS256() throws Exception {
+ testKeyEncryption_ContentEncryptionAesHmacSha(JWEConstants.RSA_OAEP, JWEConstants.A128CBC_HS256);
+ }
+
+ private void testKeyEncryption_ContentEncryptionAesGcm(String jweAlgorithmName, String jweEncryptionName) throws Exception {
+ // generate key pair for KEK
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ JWEAlgorithmProvider jweAlgorithmProvider = new RsaKeyEncryptionJWEAlgorithmProvider(getJcaAlgorithmName(jweAlgorithmName));
+ JWEEncryptionProvider jweEncryptionProvider = new AesGcmJWEEncryptionProvider(jweEncryptionName);
+
+ JWEHeader jweHeader = new JWEHeader(jweAlgorithmName, jweEncryptionName, null);
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(PAYLOAD.getBytes("UTF-8"));
+
+ jwe.getKeyStorage()
+ .setEncryptionKey(keyPair.getPublic());
+
+ String encodedContent = jwe.encodeJwe(jweAlgorithmProvider, jweEncryptionProvider);
+ System.out.println("Encoded content: " + encodedContent);
+ System.out.println("Encoded content length: " + encodedContent.length());
+
+ jwe = new JWE();
+ jwe.getKeyStorage()
+ .setDecryptionKey(keyPair.getPrivate());
+ jwe.verifyAndDecodeJwe(encodedContent, jweAlgorithmProvider, jweEncryptionProvider);
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+ System.out.println("Decoded content: " + decodedContent);
+ System.out.println("Decoded content length: " + decodedContent.length());
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+ }
+
+ private void testKeyEncryption_ContentEncryptionAesHmacSha(String jweAlgorithmName, String jweEncryptionName) throws Exception {
+ // generate key pair for KEK
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ // generate CEK
+ final SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+ final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+
+ JWEAlgorithmProvider jweAlgorithmProvider = new RsaKeyEncryptionJWEAlgorithmProvider(getJcaAlgorithmName(jweAlgorithmName));
+ JWEEncryptionProvider jweEncryptionProvider = new AesCbcHmacShaJWEEncryptionProvider(jweEncryptionName);
+
+ JWEHeader jweHeader = new JWEHeader(jweAlgorithmName, jweEncryptionName, null);
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(PAYLOAD.getBytes("UTF-8"));
+
+ jwe.getKeyStorage()
+ .setEncryptionKey(keyPair.getPublic());
+
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ String encodedContent = jwe.encodeJwe(jweAlgorithmProvider, jweEncryptionProvider);
+ System.out.println("Encoded content: " + encodedContent);
+ System.out.println("Encoded content length: " + encodedContent.length());
+
+ jwe = new JWE();
+ jwe.getKeyStorage()
+ .setDecryptionKey(keyPair.getPrivate());
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+ jwe.verifyAndDecodeJwe(encodedContent, jweAlgorithmProvider, jweEncryptionProvider);
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+ System.out.println("Decoded content: " + decodedContent);
+ System.out.println("Decoded content length: " + decodedContent.length());
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+ }
+
+ private String getJcaAlgorithmName(String jweAlgorithmName) {
+ String jcaAlgorithmName = null;
+ if (JWEConstants.RSA1_5.equals(jweAlgorithmName)) {
+ jcaAlgorithmName = "RSA/ECB/PKCS1Padding";
+ } else if (JWEConstants.RSA_OAEP.equals(jweAlgorithmName)) {
+ jcaAlgorithmName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+ }
+ return jcaAlgorithmName;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
index fbe77a2192..ef97e589ea 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
@@ -128,10 +128,19 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
@Override
public KeyWrapper getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
+ return getPublicKey(modelKey, kid, null, loader);
+ }
+
+ @Override
+ public KeyWrapper getFirstPublicKey(String modelKey, String algorithm, PublicKeyLoader loader) {
+ return getPublicKey(modelKey, null, algorithm, loader);
+ }
+
+ private KeyWrapper getPublicKey(String modelKey, String kid, String algorithm, PublicKeyLoader loader) {
// Check if key is in cache
PublicKeysEntry entry = keys.get(modelKey);
if (entry != null) {
- KeyWrapper publicKey = getPublicKey(entry.getCurrentKeys(), kid);
+ KeyWrapper publicKey = algorithm != null ? getPublicKeyByAlg(entry.getCurrentKeys(), algorithm) : getPublicKey(entry.getCurrentKeys(), kid);
if (publicKey != null) {
return publicKey;
}
@@ -157,7 +166,7 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
entry = task.get();
// Computation finished. Let's see if key is available
- KeyWrapper publicKey = getPublicKey(entry.getCurrentKeys(), kid);
+ KeyWrapper publicKey = algorithm != null ? getPublicKeyByAlg(entry.getCurrentKeys(), algorithm) : getPublicKey(entry.getCurrentKeys(), kid);
if (publicKey != null) {
return publicKey;
}
@@ -191,13 +200,18 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
}
}
+ private KeyWrapper getPublicKeyByAlg(Map publicKeys, String algorithm) {
+ if (algorithm == null) return null;
+ for(KeyWrapper keyWrapper : publicKeys.values())
+ if (algorithm.equals(keyWrapper.getAlgorithm())) return keyWrapper;
+ return null;
+ }
@Override
public void close() {
}
-
private class WrapperCallable implements Callable {
private final String modelKey;
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
index ba2dd7dbd9..c49445fffd 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
@@ -17,6 +17,7 @@
package org.keycloak.keys.infinispan;
+import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.FutureTask;
@@ -27,6 +28,7 @@ import org.keycloak.Config;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.jose.jwk.JWK;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.keys.PublicKeyStorageProviderFactory;
import org.keycloak.keys.PublicKeyStorageUtils;
@@ -105,7 +107,7 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
if (cacheKey != null) {
log.debugf("Invalidating %s from keysCache", cacheKey);
InfinispanPublicKeyStorageProvider provider = (InfinispanPublicKeyStorageProvider) cacheKey.session.getProvider(PublicKeyStorageProvider.class, getId());
- provider.addInvalidation(cacheKey.cacheKey);
+ for (String ck : cacheKey.cacheKeys) provider.addInvalidation(ck);
}
}
@@ -113,22 +115,32 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
}
private SessionAndKeyHolder getCacheKeyToInvalidate(ProviderEvent event) {
+ ArrayList cacheKeys = new ArrayList<>();
+ String cacheKey = null;
if (event instanceof RealmModel.ClientUpdatedEvent) {
RealmModel.ClientUpdatedEvent eventt = (RealmModel.ClientUpdatedEvent) event;
- String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getUpdatedClient().getRealm().getId(), eventt.getUpdatedClient().getId());
- return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getUpdatedClient().getRealm().getId(), eventt.getUpdatedClient().getId(), JWK.Use.SIG);
+ cacheKeys.add(cacheKey);
+ cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getUpdatedClient().getRealm().getId(), eventt.getUpdatedClient().getId(), JWK.Use.ENCRYPTION);
+ cacheKeys.add(cacheKey);
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKeys);
} else if (event instanceof RealmModel.ClientRemovedEvent) {
RealmModel.ClientRemovedEvent eventt = (RealmModel.ClientRemovedEvent) event;
- String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getClient().getRealm().getId(), eventt.getClient().getId());
- return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getClient().getRealm().getId(), eventt.getClient().getId(), JWK.Use.SIG);
+ cacheKeys.add(cacheKey);
+ cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getClient().getRealm().getId(), eventt.getClient().getId(), JWK.Use.ENCRYPTION);
+ cacheKeys.add(cacheKey);
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKeys);
} else if (event instanceof RealmModel.IdentityProviderUpdatedEvent) {
RealmModel.IdentityProviderUpdatedEvent eventt = (RealmModel.IdentityProviderUpdatedEvent) event;
- String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getUpdatedIdentityProvider().getInternalId());
- return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getUpdatedIdentityProvider().getInternalId());
+ cacheKeys.add(cacheKey);
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKeys);
} else if (event instanceof RealmModel.IdentityProviderRemovedEvent) {
RealmModel.IdentityProviderRemovedEvent eventt = (RealmModel.IdentityProviderRemovedEvent) event;
- String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getRemovedIdentityProvider().getInternalId());
- return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getRemovedIdentityProvider().getInternalId());
+ cacheKeys.add(cacheKey);
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKeys);
} else {
return null;
}
@@ -136,13 +148,12 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
private class SessionAndKeyHolder {
private final KeycloakSession session;
- private final String cacheKey;
+ private final ArrayList cacheKeys;
- public SessionAndKeyHolder(KeycloakSession session, String cacheKey) {
+ public SessionAndKeyHolder(KeycloakSession session, ArrayList cacheKeys) {
this.session = session;
- this.cacheKey = cacheKey;
+ this.cacheKeys = cacheKeys;
}
-
}
@Override
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProvider.java b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProvider.java
new file mode 100644
index 0000000000..df4f4986fa
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.provider.Provider;
+
+public interface CekManagementProvider extends Provider {
+
+ JWEAlgorithmProvider jweAlgorithmProvider();
+
+ @Override
+ default void close() {
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProviderFactory.java
new file mode 100644
index 0000000000..5dec0cf961
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementProviderFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderFactory;
+
+public interface CekManagementProviderFactory extends ProviderFactory {
+
+ @Override
+ default void init(Config.Scope config) {
+ }
+
+ @Override
+ default void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ default void close() {
+ }
+
+}
\ No newline at end of file
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementSpi.java b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementSpi.java
new file mode 100644
index 0000000000..68d2e36c5c
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/CekManagementSpi.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+public class CekManagementSpi implements Spi{
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "cekmanagement";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return CekManagementProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return CekManagementProviderFactory.class;
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProvider.java b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProvider.java
new file mode 100644
index 0000000000..2c86714c4b
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.provider.Provider;
+
+public interface ContentEncryptionProvider extends Provider {
+
+ JWEEncryptionProvider jweEncryptionProvider();
+
+ @Override
+ default void close() {
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProviderFactory.java
new file mode 100644
index 0000000000..0edfe64816
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionProviderFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderFactory;
+
+public interface ContentEncryptionProviderFactory extends ProviderFactory {
+
+ @Override
+ default void init(Config.Scope config) {
+ }
+
+ @Override
+ default void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ default void close() {
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionSpi.java b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionSpi.java
new file mode 100644
index 0000000000..7f94ba1dca
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/crypto/ContentEncryptionSpi.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+public class ContentEncryptionSpi implements Spi {
+
+ @Override
+ public boolean isInternal() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "contentencryption";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return ContentEncryptionProvider.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return ContentEncryptionProviderFactory.class;
+ }
+
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
index 437ad239ab..8e74c0385b 100644
--- a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageProvider.java
@@ -36,6 +36,17 @@ public interface PublicKeyStorageProvider extends Provider {
*/
KeyWrapper getPublicKey(String modelKey, String kid, PublicKeyLoader loader);
+ /**
+ * Get first found public key to verify messages signed by particular client having several public keys. Used for example during JWT client authentication
+ * or to encrypt content encryption key (CEK) by particular client. Used for example during encrypting a token in JWE
+ *
+ * @param modelKey
+ * @param algorithm
+ * @param loader
+ * @return
+ */
+ KeyWrapper getFirstPublicKey(String modelKey, String algorithm, PublicKeyLoader loader);
+
/**
* Clears all the cached public keys, so they need to be loaded again
*/
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
index 52eb7db513..0e44152c49 100644
--- a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
+++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
@@ -17,17 +17,25 @@
package org.keycloak.keys;
+import org.keycloak.jose.jwk.JWK;
+
/**
* @author Marek Posolda
*/
public class PublicKeyStorageUtils {
+ static final JWK.Use DEFAULT_KEYUSE = JWK.Use.SIG;
+
public static String getClientModelCacheKey(String realmId, String clientUuid) {
- return realmId + "::client::" + clientUuid;
+ return getClientModelCacheKey(realmId, clientUuid, DEFAULT_KEYUSE);
}
public static String getIdpModelCacheKey(String realmId, String idpInternalId) {
return realmId + "::idp::" + idpInternalId;
}
+ public static String getClientModelCacheKey(String realmId, String clientUuid, JWK.Use keyUse) {
+ return realmId + "::client::" + clientUuid + "::keyuse::" + keyUse;
+ }
+
}
diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 803848c600..432ae5c08d 100755
--- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -75,4 +75,6 @@ org.keycloak.storage.client.ClientStorageProviderSpi
org.keycloak.crypto.SignatureSpi
org.keycloak.crypto.ClientSignatureVerifierSpi
org.keycloak.crypto.HashSpi
-org.keycloak.vault.VaultSpi
\ No newline at end of file
+org.keycloak.vault.VaultSpi
+org.keycloak.crypto.CekManagementSpi
+org.keycloak.crypto.ContentEncryptionSpi
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/models/TokenManager.java b/server-spi/src/main/java/org/keycloak/models/TokenManager.java
index bd0244f71f..eb0bc7398e 100644
--- a/server-spi/src/main/java/org/keycloak/models/TokenManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/TokenManager.java
@@ -43,4 +43,7 @@ public interface TokenManager {
T decodeClientJWT(String token, ClientModel client, Class clazz);
+ String encodeAndEncrypt(Token token);
+ String cekManagementAlgorithm(TokenCategory category);
+ String encryptAlgorithm(TokenCategory category);
}
diff --git a/services/src/main/java/org/keycloak/crypto/Aes128CbcHmacSha256ContentEncryptionProviderFactory.java b/services/src/main/java/org/keycloak/crypto/Aes128CbcHmacSha256ContentEncryptionProviderFactory.java
new file mode 100644
index 0000000000..2467560ed6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/Aes128CbcHmacSha256ContentEncryptionProviderFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.models.KeycloakSession;
+
+public class Aes128CbcHmacSha256ContentEncryptionProviderFactory implements ContentEncryptionProviderFactory {
+ public static final String ID = JWEConstants.A128CBC_HS256;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public ContentEncryptionProvider create(KeycloakSession session) {
+ return new AesCbcHmacShaContentEncryptionProvider(session, ID);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/Aes128GcmContentEncryptionProviderFactory.java b/services/src/main/java/org/keycloak/crypto/Aes128GcmContentEncryptionProviderFactory.java
new file mode 100644
index 0000000000..b8a13493c0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/Aes128GcmContentEncryptionProviderFactory.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.models.KeycloakSession;
+
+public class Aes128GcmContentEncryptionProviderFactory implements ContentEncryptionProviderFactory {
+ public static final String ID = JWEConstants.A128GCM;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public ContentEncryptionProvider create(KeycloakSession session) {
+ return new AesGcmContentEncryptionProvider(session, ID);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/AesCbcHmacShaContentEncryptionProvider.java b/services/src/main/java/org/keycloak/crypto/AesCbcHmacShaContentEncryptionProvider.java
new file mode 100644
index 0000000000..10808341da
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/AesCbcHmacShaContentEncryptionProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.enc.AesCbcHmacShaJWEEncryptionProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.models.KeycloakSession;
+
+public class AesCbcHmacShaContentEncryptionProvider implements ContentEncryptionProvider {
+
+ private final KeycloakSession session;
+ private final String jweAlgorithmName;
+
+ public AesCbcHmacShaContentEncryptionProvider(KeycloakSession session, String jweAlgorithmName) {
+ this.session = session;
+ this.jweAlgorithmName = jweAlgorithmName;
+ }
+
+ @Override
+ public JWEEncryptionProvider jweEncryptionProvider() {
+ return new AesCbcHmacShaJWEEncryptionProvider(jweAlgorithmName);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/AesGcmContentEncryptionProvider.java b/services/src/main/java/org/keycloak/crypto/AesGcmContentEncryptionProvider.java
new file mode 100644
index 0000000000..ffd6c80902
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/AesGcmContentEncryptionProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.enc.AesGcmJWEEncryptionProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.models.KeycloakSession;
+
+public class AesGcmContentEncryptionProvider implements ContentEncryptionProvider {
+
+ private final KeycloakSession session;
+ private final String jweAlgorithmName;
+
+ public AesGcmContentEncryptionProvider(KeycloakSession session, String jweAlgorithmName) {
+ this.session = session;
+ this.jweAlgorithmName = jweAlgorithmName;
+ }
+
+ @Override
+ public JWEEncryptionProvider jweEncryptionProvider() {
+ return new AesGcmJWEEncryptionProvider(jweAlgorithmName);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/RsaCekManagementProvider.java b/services/src/main/java/org/keycloak/crypto/RsaCekManagementProvider.java
new file mode 100644
index 0000000000..7b367158af
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/RsaCekManagementProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.RsaKeyEncryptionJWEAlgorithmProvider;
+import org.keycloak.models.KeycloakSession;
+
+public class RsaCekManagementProvider implements CekManagementProvider {
+
+ private final KeycloakSession session;
+ private final String jweAlgorithmName;
+
+ public RsaCekManagementProvider(KeycloakSession session, String jweAlgorithmName) {
+ this.session = session;
+ this.jweAlgorithmName = jweAlgorithmName;
+ }
+
+ @Override
+ public JWEAlgorithmProvider jweAlgorithmProvider() {
+ String jcaAlgorithmName = null;
+ if (JWEConstants.RSA1_5.equals(jweAlgorithmName)) {
+ jcaAlgorithmName = "RSA/ECB/PKCS1Padding";
+ } else if (JWEConstants.RSA_OAEP.equals(jweAlgorithmName)) {
+ jcaAlgorithmName = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+ }
+ return new RsaKeyEncryptionJWEAlgorithmProvider(jcaAlgorithmName);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/RsaesOaepCekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/RsaesOaepCekManagementProviderFactory.java
new file mode 100644
index 0000000000..a23fc14dbf
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/RsaesOaepCekManagementProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.models.KeycloakSession;
+
+public class RsaesOaepCekManagementProviderFactory implements CekManagementProviderFactory {
+
+ public static final String ID = JWEConstants.RSA_OAEP;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public CekManagementProvider create(KeycloakSession session) {
+ return new RsaCekManagementProvider(session, ID);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/RsaesPkcs1CekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/RsaesPkcs1CekManagementProviderFactory.java
new file mode 100644
index 0000000000..2b52c36d48
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/RsaesPkcs1CekManagementProviderFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.crypto;
+
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.models.KeycloakSession;
+
+public class RsaesPkcs1CekManagementProviderFactory implements CekManagementProviderFactory {
+
+ public static final String ID = JWEConstants.RSA1_5;
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public CekManagementProvider create(KeycloakSession session) {
+ return new RsaCekManagementProvider(session, ID);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java b/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java
index df9c5e14ee..651af64db8 100644
--- a/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java
+++ b/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java
@@ -16,19 +16,34 @@
*/
package org.keycloak.jose.jws;
+import java.io.UnsupportedEncodingException;
+import java.security.Key;
+import java.util.LinkedList;
+import java.util.List;
+
import org.jboss.logging.Logger;
import org.keycloak.Token;
import org.keycloak.TokenCategory;
import org.keycloak.crypto.Algorithm;
+import org.keycloak.crypto.CekManagementProvider;
import org.keycloak.crypto.ClientSignatureVerifierProvider;
+import org.keycloak.crypto.ContentEncryptionProvider;
import org.keycloak.crypto.KeyUse;
+import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureSignerContext;
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.JWEException;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.keys.loader.PublicKeyStorageManager;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.TokenManager;
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
+import org.keycloak.util.TokenUtil;
public class DefaultTokenManager implements TokenManager {
@@ -142,4 +157,86 @@ public class DefaultTokenManager implements TokenManager {
return DEFAULT_ALGORITHM_NAME;
}
+ @Override
+ public String encodeAndEncrypt(Token token) {
+ String encodedToken = encode(token);
+ if (isTokenEncryptRequired(token.getCategory())) {
+ encodedToken = getEncryptedToken(token.getCategory(), encodedToken);
+ }
+ return encodedToken;
+ }
+
+ private boolean isTokenEncryptRequired(TokenCategory category) {
+ if (cekManagementAlgorithm(category) == null) return false;
+ if (encryptAlgorithm(category) == null) return false;
+ return true;
+ }
+
+ private String getEncryptedToken(TokenCategory category, String encodedToken) {
+ String encryptedToken = null;
+
+ String algAlgorithm = cekManagementAlgorithm(category);
+ String encAlgorithm = encryptAlgorithm(category);
+
+ CekManagementProvider cekManagementProvider = session.getProvider(CekManagementProvider.class, algAlgorithm);
+ JWEAlgorithmProvider jweAlgorithmProvider = cekManagementProvider.jweAlgorithmProvider();
+
+ ContentEncryptionProvider contentEncryptionProvider = session.getProvider(ContentEncryptionProvider.class, encAlgorithm);
+ JWEEncryptionProvider jweEncryptionProvider = contentEncryptionProvider.jweEncryptionProvider();
+
+ ClientModel client = session.getContext().getClient();
+
+ KeyWrapper keyWrapper = PublicKeyStorageManager.getClientPublicKeyWrapper(session, client, JWK.Use.ENCRYPTION, algAlgorithm);
+ if (keyWrapper == null) {
+ throw new RuntimeException("can not get encryption KEK");
+ }
+ Key encryptionKek = keyWrapper.getPublicKey();
+ String encryptionKekId = keyWrapper.getKid();
+ try {
+ encryptedToken = TokenUtil.jweKeyEncryptionEncode(encryptionKek, encodedToken.getBytes("UTF-8"), algAlgorithm, encAlgorithm, encryptionKekId, jweAlgorithmProvider, jweEncryptionProvider);
+ } catch (JWEException | UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ return encryptedToken;
+ }
+
+ @Override
+ public String cekManagementAlgorithm(TokenCategory category) {
+ if (category == null) return null;
+ switch (category) {
+ case ID:
+ return getCekManagementAlgorithm(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG);
+ default:
+ return null;
+ }
+ }
+
+ private String getCekManagementAlgorithm(String clientAttribute) {
+ ClientModel client = session.getContext().getClient();
+ String algorithm = client != null && clientAttribute != null ? client.getAttribute(clientAttribute) : null;
+ if (algorithm != null && !algorithm.equals("")) {
+ return algorithm;
+ }
+ return null;
+ }
+
+ @Override
+ public String encryptAlgorithm(TokenCategory category) {
+ if (category == null) return null;
+ switch (category) {
+ case ID:
+ return getEncryptAlgorithm(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC);
+ default:
+ return null;
+ }
+ }
+
+ private String getEncryptAlgorithm(String clientAttribute) {
+ ClientModel client = session.getContext().getClient();
+ String algorithm = client != null && clientAttribute != null ? client.getAttribute(clientAttribute) : null;
+ if (algorithm != null && !algorithm.equals("")) {
+ return algorithm;
+ }
+ return null;
+ }
}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
index 0181893829..e178ab16ba 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
@@ -66,8 +66,8 @@ public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
key.setType(KeyType.EC);
key.setAlgorithm(AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToAlgorithm(ecInNistRep));
key.setStatus(status);
- key.setSignKey(keyPair.getPrivate());
- key.setVerifyKey(keyPair.getPublic());
+ key.setPrivateKey(keyPair.getPrivate());
+ key.setPublicKey(keyPair.getPublic());
return key;
}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
index f8a929e01e..940d47d2a5 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractRsaKeyProvider.java
@@ -75,8 +75,8 @@ public abstract class AbstractRsaKeyProvider implements KeyProvider {
key.setType(KeyType.RSA);
key.setAlgorithm(algorithm);
key.setStatus(status);
- key.setSignKey(keyPair.getPrivate());
- key.setVerifyKey(keyPair.getPublic());
+ key.setPrivateKey(keyPair.getPrivate());
+ key.setPublicKey(keyPair.getPublic());
key.setCertificate(certificate);
return key;
diff --git a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
index e4b1037ff7..a4a362c199 100644
--- a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
+++ b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java
@@ -148,7 +148,7 @@ public class DefaultKeyManager implements KeyManager {
@Deprecated
public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
- return new ActiveRsaKey(key.getKid(), (PrivateKey) key.getSignKey(), (PublicKey) key.getVerifyKey(), key.getCertificate());
+ return new ActiveRsaKey(key.getKid(), (PrivateKey) key.getPrivateKey(), (PublicKey) key.getPublicKey(), key.getCertificate());
}
@Override
@@ -169,7 +169,7 @@ public class DefaultKeyManager implements KeyManager {
@Deprecated
public PublicKey getRsaPublicKey(RealmModel realm, String kid) {
KeyWrapper key = getKey(realm, kid, KeyUse.SIG, Algorithm.RS256);
- return key != null ? (PublicKey) key.getVerifyKey() : null;
+ return key != null ? (PublicKey) key.getPublicKey() : null;
}
@Override
@@ -200,7 +200,7 @@ public class DefaultKeyManager implements KeyManager {
for (KeyWrapper key : getKeys(realm, KeyUse.SIG, Algorithm.RS256)) {
RsaKeyMetadata m = new RsaKeyMetadata();
m.setCertificate(key.getCertificate());
- m.setPublicKey((PublicKey) key.getVerifyKey());
+ m.setPublicKey((PublicKey) key.getPublicKey());
m.setKid(key.getKid());
m.setProviderId(key.getProviderId());
m.setProviderPriority(key.getProviderPriority());
diff --git a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
index 23f43d73db..6ec2884e1f 100644
--- a/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/ClientPublicKeyLoader.java
@@ -52,12 +52,19 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
private final KeycloakSession session;
private final ClientModel client;
+ private final JWK.Use keyUse;
public ClientPublicKeyLoader(KeycloakSession session, ClientModel client) {
this.session = session;
this.client = client;
+ this.keyUse = JWK.Use.SIG;
}
+ public ClientPublicKeyLoader(KeycloakSession session, ClientModel client, JWK.Use keyUse) {
+ this.session = session;
+ this.client = client;
+ this.keyUse = keyUse;
+ }
@Override
public Map loadKeys() throws Exception {
@@ -66,8 +73,8 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
String jwksUrl = config.getJwksUrl();
jwksUrl = ResolveRelative.resolveRelativeUri(session.getContext().getUri().getRequestUri(), client.getRootUrl(), jwksUrl);
JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl);
- return JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.SIG);
- } else {
+ return JWKSUtils.getKeyWrappersForUse(jwks, keyUse);
+ } else if (keyUse == JWK.Use.SIG) {
try {
CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, JWTClientAuthenticator.ATTR_PREFIX);
KeyWrapper publicKey = getSignatureValidationKey(certInfo);
@@ -76,7 +83,9 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
logger.warnf(me, "Unable to retrieve publicKey for verify signature of client '%s' . Error details: %s", client.getClientId(), me.getMessage());
return Collections.emptyMap();
}
-
+ } else {
+ logger.warnf("Unable to retrieve publicKey of client '%s' for the specified purpose other than verifying signature", client.getClientId());
+ return Collections.emptyMap();
}
}
@@ -102,14 +111,14 @@ public class ClientPublicKeyLoader implements PublicKeyLoader {
// Check if we have kid in DB, generate otherwise
kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(clientCert.getPublicKey());
keyWrapper.setKid(kid);
- keyWrapper.setVerifyKey(clientCert.getPublicKey());
+ keyWrapper.setPublicKey(clientCert.getPublicKey());
keyWrapper.setCertificate(clientCert);
} else {
PublicKey publicKey = KeycloakModelUtils.getPublicKey(encodedPublicKey);
// Check if we have kid in DB, generate otherwise
kid = certInfo.getKid() != null ? certInfo.getKid() : KeyUtils.createKeyId(publicKey);
keyWrapper.setKid(kid);
- keyWrapper.setVerifyKey(publicKey);
+ keyWrapper.setPublicKey(publicKey);
}
return keyWrapper;
}
diff --git a/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java
index 846559ab16..d28377247e 100644
--- a/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/HardcodedPublicKeyLoader.java
@@ -53,7 +53,7 @@ public class HardcodedPublicKeyLoader implements PublicKeyLoader {
keyWrapper.setType(KeyType.RSA);
keyWrapper.setAlgorithm(Algorithm.RS256);
keyWrapper.setUse(KeyUse.SIG);
- keyWrapper.setVerifyKey(PemUtils.decodePublicKey(pem));
+ keyWrapper.setPublicKey(PemUtils.decodePublicKey(pem));
}
return keyWrapper;
}
diff --git a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
index 7bc0ec0745..45359187da 100644
--- a/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
+++ b/services/src/main/java/org/keycloak/keys/loader/OIDCIdentityProviderPublicKeyLoader.java
@@ -84,7 +84,7 @@ public class OIDCIdentityProviderPublicKeyLoader implements PublicKeyLoader {
keyWrapper.setType(KeyType.RSA);
keyWrapper.setAlgorithm(Algorithm.RS256);
keyWrapper.setUse(KeyUse.SIG);
- keyWrapper.setVerifyKey(publicKey);
+ keyWrapper.setPublicKey(publicKey);
} else {
logger.warnf("No public key saved on identityProvider %s", config.getAlias());
}
diff --git a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
index b9e522257a..4de15027d9 100644
--- a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
+++ b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
@@ -20,6 +20,7 @@ package org.keycloak.keys.loader;
import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.crypto.KeyWrapper;
+import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.keys.PublicKeyStorageProvider;
@@ -41,7 +42,7 @@ public class PublicKeyStorageManager {
KeyWrapper keyWrapper = getClientPublicKeyWrapper(session, client, input);
PublicKey publicKey = null;
if (keyWrapper != null) {
- publicKey = (PublicKey)keyWrapper.getVerifyKey();
+ publicKey = (PublicKey)keyWrapper.getPublicKey();
}
return publicKey;
}
@@ -54,6 +55,13 @@ public class PublicKeyStorageManager {
return keyStorage.getPublicKey(modelKey, kid, loader);
}
+ public static KeyWrapper getClientPublicKeyWrapper(KeycloakSession session, ClientModel client, JWK.Use keyUse, String algAlgorithm) {
+ PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
+ String modelKey = PublicKeyStorageUtils.getClientModelCacheKey(client.getRealm().getId(), client.getId(), keyUse);
+ ClientPublicKeyLoader loader = new ClientPublicKeyLoader(session, client, keyUse);
+ return keyStorage.getFirstPublicKey(modelKey, algAlgorithm, loader);
+ }
+
public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) {
boolean keyIdSetInConfiguration = idpConfig.getPublicKeySignatureVerifierKeyId() != null
&& ! idpConfig.getPublicKeySignatureVerifierKeyId().trim().isEmpty();
@@ -80,6 +88,6 @@ public class PublicKeyStorageManager {
: kid, pem);
}
- return (PublicKey)keyStorage.getPublicKey(modelKey, kid, loader).getVerifyKey();
+ return (PublicKey)keyStorage.getPublicKey(modelKey, kid, loader).getPublicKey();
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 859ab007d8..cfc396c286 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -133,6 +133,22 @@ public class OIDCAdvancedConfigWrapper {
setAttribute(OIDCConfigAttributes.ID_TOKEN_SIGNED_RESPONSE_ALG, algName);
}
+ public String getIdTokenEncryptedResponseAlg() {
+ return getAttribute(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG);
+ }
+
+ public void setIdTokenEncryptedResponseAlg(String algName) {
+ setAttribute(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG, algName);
+ }
+
+ public String getIdTokenEncryptedResponseEnc() {
+ return getAttribute(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC);
+ }
+
+ public void setIdTokenEncryptedResponseEnc(String encName) {
+ setAttribute(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC, encName);
+ }
+
private String getAttribute(String attrKey) {
if (clientModel != null) {
return clientModel.getAttribute(attrKey);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
index 7d55a6d731..d1d52266e9 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCConfigAttributes.java
@@ -37,6 +37,10 @@ public final class OIDCConfigAttributes {
public static final String ID_TOKEN_SIGNED_RESPONSE_ALG = "id.token.signed.response.alg";
+ public static final String ID_TOKEN_ENCRYPTED_RESPONSE_ALG = "id.token.encrypted.response.alg";
+
+ public static final String ID_TOKEN_ENCRYPTED_RESPONSE_ENC = "id.token.encrypted.response.enc";
+
public static final String ACCESS_TOKEN_SIGNED_RESPONSE_ALG = "access.token.signed.response.alg";
public static final String ACCESS_TOKEN_LIFESPAN = "access.token.lifespan";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index b23486cc48..757f4b7a1c 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -199,12 +199,12 @@ public class OIDCLoginProtocolService {
public Response certs() {
List keys = new LinkedList<>();
for (KeyWrapper k : session.keys().getKeys(realm)) {
- if (k.getStatus().isEnabled() && k.getUse().equals(KeyUse.SIG) && k.getVerifyKey() != null) {
+ if (k.getStatus().isEnabled() && k.getUse().equals(KeyUse.SIG) && k.getPublicKey() != null) {
JWKBuilder b = JWKBuilder.create().kid(k.getKid()).algorithm(k.getAlgorithm());
if (k.getType().equals(KeyType.RSA)) {
- keys.add(b.rsa(k.getVerifyKey(), k.getCertificate()));
+ keys.add(b.rsa(k.getPublicKey(), k.getCertificate()));
} else if (k.getType().equals(KeyType.EC)) {
- keys.add(b.ec(k.getVerifyKey()));
+ keys.add(b.ec(k.getPublicKey()));
}
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index dfe8d9e064..d65c19869b 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -20,7 +20,9 @@ package org.keycloak.protocol.oidc;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.ClientAuthenticator;
import org.keycloak.authentication.ClientAuthenticatorFactory;
+import org.keycloak.crypto.CekManagementProvider;
import org.keycloak.crypto.ClientSignatureVerifierProvider;
+import org.keycloak.crypto.ContentEncryptionProvider;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.models.ClientScopeModel;
@@ -90,6 +92,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString());
config.setIdTokenSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
+ config.setIdTokenEncryptionAlgValuesSupported(getSupportedIdTokenEncryptionAlg(false));
+ config.setIdTokenEncryptionEncValuesSupported(getSupportedIdTokenEncryptionEnc(false));
config.setUserInfoSigningAlgValuesSupported(getSupportedSigningAlgorithms(true));
config.setRequestObjectSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(true));
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
@@ -172,4 +176,26 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
}
return result;
}
+
+ private List getSupportedIdTokenEncryptionAlg(boolean includeNone) {
+ List result = new LinkedList<>();
+ for (ProviderFactory s : session.getKeycloakSessionFactory().getProviderFactories(CekManagementProvider.class)) {
+ result.add(s.getId());
+ }
+ if (includeNone) {
+ result.add("none");
+ }
+ return result;
+ }
+
+ private List getSupportedIdTokenEncryptionEnc(boolean includeNone) {
+ List result = new LinkedList<>();
+ for (ProviderFactory s : session.getKeycloakSessionFactory().getProviderFactories(ContentEncryptionProvider.class)) {
+ result.add(s.getId());
+ }
+ if (includeNone) {
+ result.add("none");
+ }
+ return result;
+ }
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index f25c3e208a..e9fba3d51e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -824,7 +824,7 @@ public class TokenManager {
idToken.setStateHash(stateHash);
}
if (idToken != null) {
- String encodedToken = session.tokens().encode(idToken);
+ String encodedToken = session.tokens().encodeAndEncrypt(idToken);
res.setIdToken(encodedToken);
}
if (refreshToken != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index f0ed3b4cbd..2f248477f6 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -411,9 +411,18 @@ public class TokenEndpoint {
if (TokenUtil.isOIDCRequest(scopeParam)) {
responseBuilder.generateIDToken();
}
-
- AccessTokenResponse res = responseBuilder.build();
-
+
+ AccessTokenResponse res = null;
+ try {
+ res = responseBuilder.build();
+ } catch (RuntimeException re) {
+ if ("can not get encryption KEK".equals(re.getMessage())) {
+ throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "can not get encryption KEK", Response.Status.BAD_REQUEST);
+ } else {
+ throw re;
+ }
+ }
+
event.success();
return cors.builder(Response.ok(res).type(MediaType.APPLICATION_JSON_TYPE)).build();
@@ -605,6 +614,7 @@ public class TokenEndpoint {
responseBuilder.generateIDToken();
}
+ // TODO : do the same as codeToToken()
AccessTokenResponse res = responseBuilder.build();
@@ -678,6 +688,7 @@ public class TokenEndpoint {
responseBuilder.generateIDToken();
}
+ // TODO : do the same as codeToToken()
AccessTokenResponse res = responseBuilder.build();
event.success();
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index 0903f0d78c..8ea4a89f69 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -71,6 +71,12 @@ public class OIDCConfigurationRepresentation {
@JsonProperty("id_token_signing_alg_values_supported")
private List idTokenSigningAlgValuesSupported;
+ @JsonProperty("id_token_encryption_alg_values_supported")
+ private List idTokenEncryptionAlgValuesSupported;
+
+ @JsonProperty("id_token_encryption_enc_values_supported")
+ private List idTokenEncryptionEncValuesSupported;
+
@JsonProperty("userinfo_signing_alg_values_supported")
private List userInfoSigningAlgValuesSupported;
@@ -224,6 +230,22 @@ public class OIDCConfigurationRepresentation {
this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported;
}
+ public List getIdTokenEncryptionAlgValuesSupported() {
+ return idTokenEncryptionAlgValuesSupported;
+ }
+
+ public void setIdTokenEncryptionAlgValuesSupported(List idTokenEncryptionAlgValuesSupported) {
+ this.idTokenEncryptionAlgValuesSupported = idTokenEncryptionAlgValuesSupported;
+ }
+
+ public List getIdTokenEncryptionEncValuesSupported() {
+ return idTokenEncryptionEncValuesSupported;
+ }
+
+ public void setIdTokenEncryptionEncValuesSupported(List idTokenEncryptionEncValuesSupported) {
+ this.idTokenEncryptionEncValuesSupported = idTokenEncryptionEncValuesSupported;
+ }
+
public List getUserInfoSigningAlgValuesSupported() {
return userInfoSigningAlgValuesSupported;
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index 2cae02c0bc..bb1d9d5284 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -125,6 +125,14 @@ public class DescriptionConverter {
configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg());
}
+ if (clientOIDC.getIdTokenEncryptedResponseAlg() != null) {
+ configWrapper.setIdTokenEncryptedResponseAlg(clientOIDC.getIdTokenEncryptedResponseAlg());
+ }
+
+ if (clientOIDC.getIdTokenEncryptedResponseEnc() != null) {
+ configWrapper.setIdTokenEncryptedResponseEnc(clientOIDC.getIdTokenEncryptedResponseEnc());
+ }
+
return client;
}
@@ -208,6 +216,12 @@ public class DescriptionConverter {
if (config.getIdTokenSignedResponseAlg() != null) {
response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg());
}
+ if (config.getIdTokenEncryptedResponseAlg() != null) {
+ response.setIdTokenEncryptedResponseAlg(config.getIdTokenEncryptedResponseAlg());
+ }
+ if (config.getIdTokenEncryptedResponseEnc() != null) {
+ response.setIdTokenEncryptedResponseEnc(config.getIdTokenEncryptedResponseEnc());
+ }
List foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
SubjectType subjectType = foundPairwiseMappers.isEmpty() ? SubjectType.PUBLIC : SubjectType.PAIRWISE;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
index d82e336367..b47de30bb0 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/KeyResource.java
@@ -70,7 +70,7 @@ public class KeyResource {
r.setStatus(key.getStatus() != null ? key.getStatus().name() : null);
r.setType(key.getType());
r.setAlgorithm(key.getAlgorithm());
- r.setPublicKey(key.getVerifyKey() != null ? PemUtils.encodeKey(key.getVerifyKey()) : null);
+ r.setPublicKey(key.getPublicKey() != null ? PemUtils.encodeKey(key.getPublicKey()) : null);
r.setCertificate(key.getCertificate() != null ? PemUtils.encodeCertificate(key.getCertificate()) : null);
keys.getKeys().add(r);
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory
new file mode 100644
index 0000000000..653d3ccd6d
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory
@@ -0,0 +1,2 @@
+org.keycloak.crypto.RsaesPkcs1CekManagementProviderFactory
+org.keycloak.crypto.RsaesOaepCekManagementProviderFactory
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.crypto.ContentEncryptionProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ContentEncryptionProviderFactory
new file mode 100644
index 0000000000..bf03c473c1
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.crypto.ContentEncryptionProviderFactory
@@ -0,0 +1,2 @@
+org.keycloak.crypto.Aes128CbcHmacSha256ContentEncryptionProviderFactory
+org.keycloak.crypto.Aes128GcmContentEncryptionProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
index 8224623979..d6811b4892 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
@@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest;
import org.keycloak.Config.Scope;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
+import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.representations.adapters.action.LogoutAction;
@@ -69,18 +70,19 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
public static class OIDCClientData {
- private KeyPair signingKeyPair;
+ private KeyPair keyPair;
private String oidcRequest;
private List sectorIdentifierRedirectUris;
- private String signingKeyType = KeyType.RSA;
- private String signingKeyAlgorithm = Algorithm.RS256;
+ private String keyType = KeyType.RSA;
+ private String keyAlgorithm = Algorithm.RS256;
+ private KeyUse keyUse = KeyUse.SIG;
public KeyPair getSigningKeyPair() {
- return signingKeyPair;
+ return keyPair;
}
public void setSigningKeyPair(KeyPair signingKeyPair) {
- this.signingKeyPair = signingKeyPair;
+ this.keyPair = signingKeyPair;
}
public String getOidcRequest() {
@@ -100,19 +102,51 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
}
public String getSigningKeyType() {
- return signingKeyType;
+ return keyType;
}
public void setSigningKeyType(String signingKeyType) {
- this.signingKeyType = signingKeyType;
+ this.keyType = signingKeyType;
}
public String getSigningKeyAlgorithm() {
- return signingKeyAlgorithm;
+ return keyAlgorithm;
}
public void setSigningKeyAlgorithm(String signingKeyAlgorithm) {
- this.signingKeyAlgorithm = signingKeyAlgorithm;
+ this.keyAlgorithm = signingKeyAlgorithm;
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+
+ public void setKeyPair(KeyPair keyPair) {
+ this.keyPair = keyPair;
+ }
+
+ public String getKeyType() {
+ return keyType;
+ }
+
+ public void setKeyType(String keyType) {
+ this.keyType = keyType;
+ }
+
+ public String getKeyAlgorithm() {
+ return keyAlgorithm;
+ }
+
+ public void setKeyAlgorithm(String keyAlgorithm) {
+ this.keyAlgorithm = keyAlgorithm;
+ }
+
+ public KeyUse getKeyUse() {
+ return keyUse;
+ }
+
+ public void setKeyUse(KeyUse keyUse) {
+ this.keyUse = keyUse;
}
}
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
index bf18472288..2831b416b2 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -25,8 +25,10 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.AsymmetricSignatureSignerContext;
import org.keycloak.crypto.KeyType;
+import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureSignerContext;
+import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
@@ -73,7 +75,8 @@ public class TestingOIDCEndpointsApplicationResource {
public Map generateKeys(@QueryParam("jwaAlgorithm") String jwaAlgorithm) {
try {
KeyPair keyPair = null;
- if (jwaAlgorithm == null) jwaAlgorithm = org.keycloak.crypto.Algorithm.RS256;
+ KeyUse keyUse = KeyUse.SIG;
+ if (jwaAlgorithm == null) jwaAlgorithm = Algorithm.RS256;
String keyType = null;
switch (jwaAlgorithm) {
@@ -98,13 +101,21 @@ public class TestingOIDCEndpointsApplicationResource {
keyType = KeyType.EC;
keyPair = generateEcdsaKey("secp521r1");
break;
+ case JWEConstants.RSA1_5:
+ case JWEConstants.RSA_OAEP:
+ // for JWE KEK Key Encryption
+ keyType = KeyType.RSA;
+ keyUse = KeyUse.ENC;
+ keyPair = KeyUtils.generateRsaKeyPair(2048);
+ break;
default :
throw new RuntimeException("Unsupported signature algorithm");
}
- clientData.setSigningKeyPair(keyPair);
- clientData.setSigningKeyType(keyType);
- clientData.setSigningKeyAlgorithm(jwaAlgorithm);
+ clientData.setKeyPair(keyPair);
+ clientData.setKeyType(keyType);
+ clientData.setKeyAlgorithm(jwaAlgorithm);
+ clientData.setKeyUse(keyUse);
} catch (Exception e) {
throw new BadRequestException("Error generating signing keypair", e);
}
@@ -140,21 +151,23 @@ public class TestingOIDCEndpointsApplicationResource {
@NoCache
public JSONWebKeySet getJwks() {
JSONWebKeySet keySet = new JSONWebKeySet();
- KeyPair signingKeyPair = clientData.getSigningKeyPair();
- String signingKeyAlgorithm = clientData.getSigningKeyAlgorithm();
- String signingKeyType = clientData.getSigningKeyType();
+ KeyPair keyPair = clientData.getKeyPair();
+ String keyAlgorithm = clientData.getKeyAlgorithm();
+ String keyType = clientData.getKeyType();
+ KeyUse keyUse = clientData.getKeyUse();
- if (signingKeyPair == null || !isSupportedSigningAlgorithm(signingKeyAlgorithm)) {
+ if (keyPair == null || !isSupportedAlgorithm(keyAlgorithm)) {
keySet.setKeys(new JWK[] {});
- } else if (KeyType.RSA.equals(signingKeyType)) {
- keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(signingKeyAlgorithm).rsa(signingKeyPair.getPublic()) });
- } else if (KeyType.EC.equals(signingKeyType)) {
- keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(signingKeyAlgorithm).ec(signingKeyPair.getPublic()) });
+ } else if (KeyType.RSA.equals(keyType)) {
+ keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(keyAlgorithm).rsa(keyPair.getPublic(), keyUse) });
+ } else if (KeyType.EC.equals(keyType)) {
+ keySet.setKeys(new JWK[] { JWKBuilder.create().algorithm(keyAlgorithm).ec(keyPair.getPublic()) });
} else {
keySet.setKeys(new JWK[] {});
}
return keySet;
+
}
@@ -174,7 +187,7 @@ public class TestingOIDCEndpointsApplicationResource {
oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge));
}
- if (!isSupportedSigningAlgorithm(jwaAlgorithm)) throw new BadRequestException("Unknown argument: " + jwaAlgorithm);
+ if (!isSupportedAlgorithm(jwaAlgorithm)) throw new BadRequestException("Unknown argument: " + jwaAlgorithm);
if ("none".equals(jwaAlgorithm)) {
clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none());
@@ -186,13 +199,14 @@ public class TestingOIDCEndpointsApplicationResource {
KeyWrapper keyWrapper = new KeyWrapper();
keyWrapper.setAlgorithm(clientData.getSigningKeyAlgorithm());
keyWrapper.setKid(kid);
- keyWrapper.setSignKey(privateKey);
+ keyWrapper.setPrivateKey(privateKey);
SignatureSignerContext signer = new AsymmetricSignatureSignerContext(keyWrapper);
clientData.setOidcRequest(new JWSBuilder().kid(kid).jsonContent(oidcRequest).sign(signer));
}
}
-
- private boolean isSupportedSigningAlgorithm(String signingAlgorithm) {
+
+ private boolean isSupportedAlgorithm(String signingAlgorithm) {
+ if (signingAlgorithm == null) return false;
boolean ret = false;
switch (signingAlgorithm) {
case "none":
@@ -205,6 +219,8 @@ public class TestingOIDCEndpointsApplicationResource {
case Algorithm.ES256:
case Algorithm.ES384:
case Algorithm.ES512:
+ case JWEConstants.RSA1_5:
+ case JWEConstants.RSA_OAEP:
ret = true;
}
return ret;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 057cc0f630..763fd4f113 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -1236,7 +1236,7 @@ public class OAuthClient {
KeyWrapper key = new KeyWrapper();
key.setKid(k.getKeyId());
key.setAlgorithm(k.getAlgorithm());
- key.setVerifyKey(publicKey);
+ key.setPublicKey(publicKey);
key.setUse(KeyUse.SIG);
return key;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java
index e76aae146c..30d066f4af 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenSignatureUtil.java
@@ -75,6 +75,20 @@ public class TokenSignatureUtil {
clientResource.update(clientRep);
}
+ public static void changeClientIdTokenEncryptionAlgProvider(ClientResource clientResource, String toAlgName) {
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ log.tracef("change client %s id token encryption alg algorithm from %s to %s", clientRep.getClientId(), clientRep.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG), toAlgName);
+ clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ALG, toAlgName);
+ clientResource.update(clientRep);
+ }
+
+ public static void changeClientIdTokenEncryptionEncProvider(ClientResource clientResource, String toEncName) {
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ log.tracef("change client %s id token encryption enc algorithm from %s to %s", clientRep.getClientId(), clientRep.getAttributes().get(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC), toEncName);
+ clientRep.getAttributes().put(OIDCConfigAttributes.ID_TOKEN_ENCRYPTED_RESPONSE_ENC, toEncName);
+ clientResource.update(clientRep);
+ }
+
public static boolean verifySignature(String sigAlgName, String token, Keycloak adminClient) throws Exception {
PublicKey publicKey = getRealmPublicKey(TEST_REALM_NAME, sigAlgName, adminClient);
JWSInput jws = new JWSInput(token);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index 93da6b3133..e29c97ef6a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -27,6 +27,7 @@ import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.client.registration.HttpErrorException;
import org.keycloak.common.util.CollectionUtil;
import org.keycloak.events.Errors;
+import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -305,6 +306,47 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
}
+ @Test
+ public void testIdTokenEncryptedResponse() throws Exception {
+ OIDCClientRepresentation response = null;
+ OIDCClientRepresentation updated = null;
+ try {
+ // create (no specification)
+ OIDCClientRepresentation clientRep = createRep();
+
+ response = reg.oidc().create(clientRep);
+ Assert.assertEquals(Boolean.FALSE, response.getTlsClientCertificateBoundAccessTokens());
+ Assert.assertNotNull(response.getClientSecret());
+
+ // Test Keycloak representation
+ ClientRepresentation kcClient = getClient(response.getClientId());
+ OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
+ Assert.assertNull(config.getIdTokenEncryptedResponseAlg());
+ Assert.assertNull(config.getIdTokenEncryptedResponseEnc());
+
+ // update (alg RSA1_5, enc A128CBC-HS256)
+ reg.auth(Auth.token(response));
+ response.setIdTokenEncryptedResponseAlg(JWEConstants.RSA1_5);
+ response.setIdTokenEncryptedResponseEnc(JWEConstants.A128CBC_HS256);
+ updated = reg.oidc().update(response);
+ Assert.assertEquals(JWEConstants.RSA1_5, updated.getIdTokenEncryptedResponseAlg());
+ Assert.assertEquals(JWEConstants.A128CBC_HS256, updated.getIdTokenEncryptedResponseEnc());
+
+ // Test Keycloak representation
+ kcClient = getClient(updated.getClientId());
+ config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
+ Assert.assertEquals(JWEConstants.RSA1_5, config.getIdTokenEncryptedResponseAlg());
+ Assert.assertEquals(JWEConstants.A128CBC_HS256, config.getIdTokenEncryptedResponseEnc());
+
+ } finally {
+ // revert
+ reg.auth(Auth.token(updated));
+ updated.setIdTokenEncryptedResponseAlg(null);
+ updated.setIdTokenEncryptedResponseEnc(null);
+ reg.oidc().update(updated);
+ }
+ }
+
@Test
public void testOIDCEndpointCreateWithSamlClient() throws Exception {
ClientsResource clientsResource = adminClient.realm(TEST).clients();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/IdTokenEncryptionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/IdTokenEncryptionTest.java
new file mode 100644
index 0000000000..f17a0e5942
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/IdTokenEncryptionTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.oidc;
+
+import java.io.UnsupportedEncodingException;
+import java.security.PrivateKey;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.crypto.AesCbcHmacShaContentEncryptionProvider;
+import org.keycloak.crypto.AesGcmContentEncryptionProvider;
+import org.keycloak.crypto.Algorithm;
+import org.keycloak.crypto.RsaCekManagementProvider;
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.JWEException;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.representations.IDToken;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.AbstractAdminTest;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
+import org.keycloak.testsuite.util.ClientManager;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
+import org.keycloak.testsuite.util.TokenSignatureUtil;
+import org.keycloak.util.TokenUtil;
+
+public class IdTokenEncryptionTest extends AbstractTestRealmKeycloakTest {
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected AppPage appPage;
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Page
+ protected AccountUpdateProfilePage profilePage;
+
+ @Page
+ protected OAuthGrantPage grantPage;
+
+ @Page
+ protected ErrorPage errorPage;
+
+ @Deployment
+ public static WebArchive deploy() {
+ return RunOnServerDeployment.create(OIDCAdvancedRequestParamsTest.class, AbstractTestRealmKeycloakTest.class);
+ }
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ }
+
+ @Before
+ public void clientConfiguration() {
+ ClientManager.realm(adminClient.realm("test")).clientId("test-app").directAccessGrant(true);
+ /*
+ * Configure the default client ID. Seems like OAuthClient is keeping the state of clientID
+ * For example: If some test case configure oauth.clientId("sample-public-client"), other tests
+ * will faile and the clientID will always be "sample-public-client
+ * @see AccessTokenTest#testAuthorizationNegotiateHeaderIgnored()
+ */
+ oauth.clientId("test-app");
+ oauth.maxAge(null);
+ }
+
+ @Override
+ public void addTestRealms(List testRealms) {
+ RealmRepresentation realm = AbstractAdminTest.loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+ testRealms.add(realm);
+ }
+
+ @Test
+ public void testIdTokenEncryptionAlgRSA1_5EncA128CBC_HS256() {
+ // add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
+ TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
+ testIdTokenSignatureAndEncryption(Algorithm.ES256, JWEConstants.RSA1_5, JWEConstants.A128CBC_HS256);
+ }
+
+ @Test
+ public void testIdTokenEncryptionAlgRSA1_5EncA128GCM() {
+ testIdTokenSignatureAndEncryption(Algorithm.RS384, JWEConstants.RSA1_5, JWEConstants.A128GCM);
+ }
+
+ @Test
+ public void testIdTokenEncryptionAlgRSA_OAEPEncA128CBC_HS256() {
+ // add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
+ TokenSignatureUtil.registerKeyProvider("P-521", adminClient, testContext);
+ testIdTokenSignatureAndEncryption(Algorithm.ES512, JWEConstants.RSA_OAEP, JWEConstants.A128CBC_HS256);
+ }
+
+ @Test
+ public void testIdTokenEncryptionAlgRSA_OAEPEncA128GCM() {
+ // add key provider explicitly though DefaultKeyManager create fallback key provider if not exist
+ TokenSignatureUtil.registerKeyProvider("P-256", adminClient, testContext);
+ testIdTokenSignatureAndEncryption(Algorithm.ES256, JWEConstants.RSA_OAEP, JWEConstants.A128GCM);
+ }
+
+ private void testIdTokenSignatureAndEncryption(String sigAlgorithm, String algAlgorithm, String encAlgorithm) {
+ ClientResource clientResource = null;
+ ClientRepresentation clientRep = null;
+ try {
+ // generate and register encryption key onto client
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.generateKeys(algAlgorithm);
+
+ clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ clientRep = clientResource.toRepresentation();
+ // set id token signature algorithm and encryption algorithms
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenSignedResponseAlg(sigAlgorithm);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseAlg(algAlgorithm);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseEnc(encAlgorithm);
+ // use and set jwks_url
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
+ String jwksUrl = TestApplicationResourceUrls.clientJwksUri();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl);
+ clientResource.update(clientRep);
+
+ // get id token
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ String code = response.getCode();
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+
+ // parse JWE and JOSE Header
+ String jweStr = tokenResponse.getIdToken();
+ String[] parts = jweStr.split("\\.");
+ Assert.assertEquals(parts.length, 5);
+
+ // get decryption key
+ // not publickey , use privateKey
+ Map keyPair = oidcClientEndpointsResource.getKeysAsPem();
+ PrivateKey decryptionKEK = PemUtils.decodePrivateKey(keyPair.get("privateKey"));
+
+ // verify and decrypt JWE
+ JWEAlgorithmProvider algorithmProvider = getJweAlgorithmProvider(algAlgorithm);
+ JWEEncryptionProvider encryptionProvider = getJweEncryptionProvider(encAlgorithm);
+ byte[] decodedString = TokenUtil.jweKeyEncryptionVerifyAndDecode(decryptionKEK, jweStr, algorithmProvider, encryptionProvider);
+ String idTokenString = new String(decodedString, "UTF-8");
+
+ // verify JWS
+ IDToken idToken = oauth.verifyIDToken(idTokenString);
+ Assert.assertEquals("test-user@localhost", idToken.getPreferredUsername());
+ Assert.assertEquals("test-app", idToken.getIssuedFor());
+ } catch (JWEException | UnsupportedEncodingException e) {
+ Assert.fail();
+ } finally {
+ clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ clientRep = clientResource.toRepresentation();
+ // revert id token signature algorithm and encryption algorithms
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenSignedResponseAlg(Algorithm.RS256);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseAlg(null);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseEnc(null);
+ // revert jwks_url settings
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(false);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(null);
+ clientResource.update(clientRep);
+ }
+ }
+
+ private JWEAlgorithmProvider getJweAlgorithmProvider(String algAlgorithm) {
+ JWEAlgorithmProvider jweAlgorithmProvider = null;
+ if (JWEConstants.RSA1_5.equals(algAlgorithm) || JWEConstants.RSA_OAEP.equals(algAlgorithm) ) {
+ jweAlgorithmProvider = new RsaCekManagementProvider(null, algAlgorithm).jweAlgorithmProvider();
+ }
+ return jweAlgorithmProvider;
+ }
+ private JWEEncryptionProvider getJweEncryptionProvider(String encAlgorithm) {
+ JWEEncryptionProvider jweEncryptionProvider = null;
+ if (JWEConstants.A128CBC_HS256.equals(encAlgorithm)) {
+ jweEncryptionProvider = new AesCbcHmacShaContentEncryptionProvider(null, encAlgorithm).jweEncryptionProvider();
+ } else if (JWEConstants.A128GCM.equals(encAlgorithm)) {
+ jweEncryptionProvider = new AesGcmContentEncryptionProvider(null, encAlgorithm).jweEncryptionProvider();
+ }
+ return jweEncryptionProvider;
+ }
+
+ @Test
+ @UncaughtServerErrorExpected
+ public void testIdTokenEncryptionWithoutEncryptionKEK() {
+ ClientResource clientResource = null;
+ ClientRepresentation clientRep = null;
+ try {
+ // generate and register signing/verifying key onto client, not encryption key
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.generateKeys(Algorithm.RS256);
+
+ clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ clientRep = clientResource.toRepresentation();
+ // set id token signature algorithm and encryption algorithms
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenSignedResponseAlg(Algorithm.RS256);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseAlg(JWEConstants.RSA1_5);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseEnc(JWEConstants.A128CBC_HS256);
+ // use and set jwks_url
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(true);
+ String jwksUrl = TestApplicationResourceUrls.clientJwksUri();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(jwksUrl);
+ clientResource.update(clientRep);
+
+ // get id token but failed
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ AccessTokenResponse atr = oauth.doAccessTokenRequest(response.getCode(), "password");
+ Assert.assertEquals(OAuthErrorException.INVALID_REQUEST, atr.getError());
+ Assert.assertEquals("can not get encryption KEK", atr.getErrorDescription());
+
+ } finally {
+ // Revert
+ clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenSignedResponseAlg(Algorithm.RS256);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseAlg(null);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setIdTokenEncryptedResponseEnc(null);
+ // Revert jwks_url settings
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUseJwksUrl(false);
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setJwksUrl(null);
+ clientResource.update(clientRep);
+ }
+ }
+
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index 0235b260a6..553846610f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -796,7 +796,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
assertEquals("Invalid Request", errorPage.getError());
// Generate keypair for client
- String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys(null).get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
+ String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys("RS256").get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
// Verify signed request_uri will fail due to failed signature validation
oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
index 821bde74f2..dff1da2800 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.crypto.Algorithm;
+import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -126,9 +127,13 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
// Signature algorithms
- Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
- Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
- Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), "none", Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.PS256, Algorithm.PS384, Algorithm.PS512);
+ Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), 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.getUserInfoSigningAlgValuesSupported(), "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);
+
+ // Encryption algorithms
+ Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP);
+ Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM);
// Client authentication
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt", "client_secret_jwt");
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index d4cf94d381..efe71c3915 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -340,6 +340,10 @@ access-token-signed-response-alg=Access Token Signature Algorithm
access-token-signed-response-alg.tooltip=JWA algorithm used for signing access tokens.
id-token-signed-response-alg=ID Token Signature Algorithm
id-token-signed-response-alg.tooltip=JWA algorithm used for signing ID tokens.
+id-token-encrypted-response-alg=ID Token Encryption Key Management Algorithm
+id-token-encrypted-response-alg.tooltip=JWA Algorithm used for key management in encrypting ID tokens. This option is needed just if you want encrypted ID tokens. If left empty, ID Tokens are just signed, but not encrypted.
+id-token-encrypted-response-enc=ID Token Encryption Content Encryption Algorithm
+id-token-encrypted-response-enc.tooltip=id-token-encrypted-response-enc.tooltip=JWA Algorithm used for content encryption in encrypting ID tokens. This option is needed just if you want encrypted ID tokens. If left empty, ID Tokens are just signed, but not encrypted.
user-info-signed-response-alg=User Info Signed Response Algorithm
user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format.
request-object-signature-alg=Request Object Signature Algorithm
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index d73bbf3405..f3b6877dd7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1171,6 +1171,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
$scope.accessTokenSignedResponseAlg = $scope.client.attributes['access.token.signed.response.alg'];
$scope.idTokenSignedResponseAlg = $scope.client.attributes['id.token.signed.response.alg'];
+ $scope.idTokenEncryptedResponseAlg = $scope.client.attributes['id.token.encrypted.response.alg'];
+ $scope.idTokenEncryptedResponseEnc = $scope.client.attributes['id.token.encrypted.response.enc'];
var attrVal1 = $scope.client.attributes['user.info.response.signature.alg'];
$scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1;
@@ -1293,6 +1295,14 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
$scope.clientEdit.attributes['id.token.signed.response.alg'] = $scope.idTokenSignedResponseAlg;
};
+ $scope.changeIdTokenEncryptedResponseAlg = function() {
+ $scope.clientEdit.attributes['id.token.encrypted.response.alg'] = $scope.idTokenEncryptedResponseAlg;
+ };
+
+ $scope.changeIdTokenEncryptedResponseEnc = function() {
+ $scope.clientEdit.attributes['id.token.encrypted.response.enc'] = $scope.idTokenEncryptedResponseEnc;
+ };
+
$scope.changeUserInfoSignedResponseAlg = function() {
if ($scope.userInfoSignedResponseAlg === 'unsigned') {
$scope.clientEdit.attributes['user.info.response.signature.alg'] = null;
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 0cf46a4f8b..d24f87e342 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -418,6 +418,36 @@
{{:: 'id-token-signed-response-alg.tooltip' | translate}}
+
+
+
+