diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWE.java b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
new file mode 100644
index 0000000000..03bb43f99d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017 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;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.common.util.BouncyIntegration;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author Marek Posolda
+ */
+public class JWE {
+
+ static {
+ BouncyIntegration.init();
+ }
+
+ private JWEHeader header;
+ private String base64Header;
+
+ private JWEKeyStorage keyStorage = new JWEKeyStorage();
+ private String base64Cek;
+
+ private byte[] initializationVector;
+
+ private byte[] content;
+ private byte[] encryptedContent;
+
+ private byte[] authenticationTag;
+
+ public JWE header(JWEHeader header) {
+ this.header = header;
+ this.base64Header = null;
+ return this;
+ }
+
+ JWEHeader getHeader() {
+ if (header == null && base64Header != null) {
+ try {
+ byte[] decodedHeader = Base64Url.decode(base64Header);
+ header = JsonSerialization.readValue(decodedHeader, JWEHeader.class);
+ } catch (IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ return header;
+ }
+
+ public String getBase64Header() throws IOException {
+ if (base64Header == null && header != null) {
+ byte[] contentBytes = JsonSerialization.writeValueAsBytes(header);
+ base64Header = Base64Url.encode(contentBytes);
+ }
+ return base64Header;
+ }
+
+
+ public JWEKeyStorage getKeyStorage() {
+ return keyStorage;
+ }
+
+
+ public byte[] getInitializationVector() {
+ return initializationVector;
+ }
+
+
+ public JWE content(byte[] content) {
+ this.content = content;
+ return this;
+ }
+
+ public byte[] getContent() {
+ return content;
+ }
+
+ public byte[] getEncryptedContent() {
+ return encryptedContent;
+ }
+
+
+ public byte[] getAuthenticationTag() {
+ return authenticationTag;
+ }
+
+
+ public void setEncryptedContentInfo(byte[] initializationVector, byte[] encryptedContent, byte[] authenticationTag) {
+ this.initializationVector = initializationVector;
+ this.encryptedContent = encryptedContent;
+ this.authenticationTag = authenticationTag;
+ }
+
+
+ public String encodeJwe() {
+ try {
+ if (header == null) {
+ throw new IllegalStateException("Header must be set");
+ }
+ if (content == null) {
+ 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() + "'");
+ }
+
+ keyStorage.setEncryptionProvider(encryptionProvider);
+ keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, true); // Will generate CEK if it's not already present
+
+ byte[] encodedCEK = algorithmProvider.encodeCek(encryptionProvider, keyStorage, keyStorage.getEncryptionKey());
+ base64Cek = Base64Url.encode(encodedCEK);
+
+ encryptionProvider.encodeJwe(this);
+
+ return getEncodedJweString();
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private String getEncodedJweString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(base64Header).append(".")
+ .append(base64Cek).append(".")
+ .append(Base64Url.encode(initializationVector)).append(".")
+ .append(Base64Url.encode(encryptedContent)).append(".")
+ .append(Base64Url.encode(authenticationTag));
+
+ return builder.toString();
+ }
+
+
+ public JWE verifyAndDecodeJwe(String jweStr) {
+ try {
+ 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();
+ 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;
+ } catch (IOException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
new file mode 100644
index 0000000000..d81141dbd1
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017 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;
+
+/**
+ * @author Marek Posolda
+ */
+public class JWEConstants {
+
+ public static final String DIR = "dir";
+ public static final String A128KW = "A128KW";
+
+ public static final String A128CBC_HS256 = "A128CBC-HS256";
+ public static final String A192CBC_HS384 = "A192CBC-HS384";
+ public static final String A256CBC_HS512 = "A256CBC-HS512";
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
new file mode 100644
index 0000000000..30b5150a46
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 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;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author Marek Posolda
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JWEHeader implements Serializable {
+
+ @JsonProperty("alg")
+ private String algorithm;
+
+ @JsonProperty("enc")
+ private String encryptionAlgorithm;
+
+ @JsonProperty("zip")
+ private String compressionAlgorithm;
+
+
+ @JsonProperty("typ")
+ private String type;
+
+ @JsonProperty("cty")
+ private String contentType;
+
+ @JsonProperty("kid")
+ private String keyId;
+
+ public JWEHeader() {
+ }
+
+ public JWEHeader(String algorithm, String encryptionAlgorithm, String compressionAlgorithm) {
+ this.algorithm = algorithm;
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ this.compressionAlgorithm = compressionAlgorithm;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public String getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ public String getCompressionAlgorithm() {
+ return compressionAlgorithm;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ static {
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+
+ }
+
+ public String toString() {
+ try {
+ return mapper.writeValueAsString(this);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
new file mode 100644
index 0000000000..9ce042ce03
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEKeyStorage.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 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;
+
+import java.security.Key;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class JWEKeyStorage {
+
+ private Key encryptionKey;
+
+ private byte[] cekBytes;
+
+ private Map decodedCEK = new HashMap<>();
+
+ private JWEEncryptionProvider encryptionProvider;
+
+
+ public Key getEncryptionKey() {
+ return encryptionKey;
+ }
+
+ public JWEKeyStorage setEncryptionKey(Key encryptionKey) {
+ this.encryptionKey = encryptionKey;
+ return this;
+ }
+
+
+ public void setCEKBytes(byte[] cekBytes) {
+ this.cekBytes = cekBytes;
+ }
+
+ public byte[] getCekBytes() {
+ if (cekBytes == null) {
+ cekBytes = encryptionProvider.serializeCEK(this);
+ }
+ return cekBytes;
+ }
+
+ public JWEKeyStorage setCEKKey(Key key, KeyUse keyUse) {
+ decodedCEK.put(keyUse, key);
+ return this;
+ }
+
+
+ public Key getCEKKey(KeyUse keyUse, boolean generateIfNotPresent) {
+ Key key = decodedCEK.get(keyUse);
+ if (key == null) {
+ if (encryptionProvider != null) {
+
+ if (cekBytes == null && generateIfNotPresent) {
+ generateCekBytes();
+ }
+
+ if (cekBytes != null) {
+ encryptionProvider.deserializeCEK(this);
+ }
+ } else {
+ throw new IllegalStateException("encryptionProvider needs to be set");
+ }
+ }
+
+ return decodedCEK.get(keyUse);
+ }
+
+
+ private void generateCekBytes() {
+ int cekLength = encryptionProvider.getExpectedCEKLength();
+ cekBytes = JWEUtils.generateSecret(cekLength);
+ }
+
+
+ public void setEncryptionProvider(JWEEncryptionProvider encryptionProvider) {
+ this.encryptionProvider = encryptionProvider;
+ }
+
+
+ public enum KeyUse {
+ ENCRYPTION,
+ SIGNATURE
+ }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
new file mode 100644
index 0000000000..80aaea5a87
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwe.alg.AesKeyWrapAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.DirectAlgorithmProvider;
+import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
+import org.keycloak.jose.jwe.enc.AesCbcHmacShaEncryptionProvider;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ *
+ * @author Marek Posolda
+ */
+class JWERegistry {
+
+ // https://tools.ietf.org/html/rfc7518#page-12
+ // Registry not pluggable for now. Just supported algorithms included
+ private static final Map ENC_PROVIDERS = new HashMap<>();
+
+ // https://tools.ietf.org/html/rfc7518#page-22
+ // Registry not pluggable for now. Just supported algorithms included
+ private static final Map ALG_PROVIDERS = new HashMap<>();
+
+
+ static {
+ // Provider 'dir' just directly uses encryption keys for encrypt/decrypt content.
+ ALG_PROVIDERS.put(JWEConstants.DIR, new DirectAlgorithmProvider());
+ ALG_PROVIDERS.put(JWEConstants.A128KW, new AesKeyWrapAlgorithmProvider());
+
+
+ ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
+ ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());
+ ENC_PROVIDERS.put(JWEConstants.A256CBC_HS512, new AesCbcHmacShaEncryptionProvider.Aes256CbcHmacSha512Provider());
+ }
+
+
+ static JWEAlgorithmProvider getAlgProvider(String alg) {
+ return ALG_PROVIDERS.get(alg);
+ }
+
+
+ static JWEEncryptionProvider getEncProvider(String enc) {
+ return ENC_PROVIDERS.get(enc);
+ }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java b/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
new file mode 100644
index 0000000000..d88ec56ac9
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 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;
+
+import java.security.SecureRandom;
+
+/**
+ * @author Marek Posolda
+ */
+public class JWEUtils {
+
+ private JWEUtils() {
+ }
+
+ public static byte[] generateSecret(int bytes) {
+ byte[] buf = new byte[bytes];
+ new SecureRandom().nextBytes(buf);
+ return buf;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java
new file mode 100644
index 0000000000..2a6eedebb8
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/AesKeyWrapAlgorithmProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.Wrapper;
+import org.bouncycastle.crypto.engines.AESWrapEngine;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
+
+ @Override
+ public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException {
+ try {
+ Wrapper encrypter = new AESWrapEngine();
+ encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
+ return encrypter.unwrap(encodedCek, 0, encodedCek.length);
+ } catch (InvalidCipherTextException icte) {
+ throw new IllegalStateException(icte);
+ }
+ }
+
+ @Override
+ public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
+ Wrapper encrypter = new AESWrapEngine();
+ encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
+ byte[] cekBytes = keyStorage.getCekBytes();
+ return encrypter.wrap(cekBytes, 0, cekBytes.length);
+ }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
new file mode 100644
index 0000000000..98ab7b39a9
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public class DirectAlgorithmProvider implements JWEAlgorithmProvider {
+
+ @Override
+ public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException {
+ return new byte[0];
+ }
+
+ @Override
+ public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException {
+ return new byte[0];
+ }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
new file mode 100644
index 0000000000..057f487c60
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
+
+/**
+ * @author Marek Posolda
+ */
+public interface JWEAlgorithmProvider {
+
+ byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws IOException, GeneralSecurityException;
+
+ byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws IOException, GeneralSecurityException;
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java
new file mode 100644
index 0000000000..dcec260137
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/AesCbcHmacShaEncryptionProvider.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2017 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.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+import org.keycloak.jose.jwe.JWEUtils;
+
+/**
+ * @author Marek Posolda
+ */
+public abstract class AesCbcHmacShaEncryptionProvider implements JWEEncryptionProvider {
+
+
+ @Override
+ public void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
+
+ byte[] contentBytes = jwe.getContent();
+
+ byte[] initializationVector = JWEUtils.generateSecret(16);
+
+ Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+ if (aesKey == null) {
+ throw new IllegalArgumentException("AES CEK key not present");
+ }
+
+ Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+ if (hmacShaKey == null) {
+ throw new IllegalArgumentException("HMAC 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);
+ }
+
+ byte[] cipherBytes = encryptBytes(contentBytes, initializationVector, aesKey);
+
+ byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+ byte[] authenticationTag = computeAuthenticationTag(aad, initializationVector, cipherBytes, hmacShaKey);
+
+ jwe.setEncryptedContentInfo(initializationVector, cipherBytes, authenticationTag);
+ }
+
+
+ @Override
+ public void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException {
+ Key aesKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, false);
+ if (aesKey == null) {
+ throw new IllegalArgumentException("AES CEK key not present");
+ }
+
+ Key hmacShaKey = jwe.getKeyStorage().getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+ if (hmacShaKey == null) {
+ throw new IllegalArgumentException("HMAC 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);
+ }
+
+ byte[] aad = jwe.getBase64Header().getBytes("UTF-8");
+ byte[] authenticationTag = computeAuthenticationTag(aad, jwe.getInitializationVector(), jwe.getEncryptedContent(), hmacShaKey);
+
+ byte[] expectedAuthTag = jwe.getAuthenticationTag();
+ boolean digitsEqual = MessageDigest.isEqual(expectedAuthTag, authenticationTag);
+
+ if (!digitsEqual) {
+ throw new IllegalArgumentException("Signature validations failed");
+ }
+
+ byte[] contentBytes = decryptBytes(jwe.getEncryptedContent(), jwe.getInitializationVector(), aesKey);
+
+ jwe.content(contentBytes);
+ }
+
+
+ protected abstract int getExpectedAesKeyLength();
+
+ protected abstract String getHmacShaAlgorithm();
+
+ protected abstract int getAuthenticationTagLength();
+
+
+ private byte[] encryptBytes(byte[] contentBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+ AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
+ cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParamSpec);
+ return cipher.doFinal(contentBytes);
+ }
+
+
+ private byte[] decryptBytes(byte[] encryptedBytes, byte[] ivBytes, Key aesKey) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
+ AlgorithmParameterSpec ivParamSpec = new IvParameterSpec(ivBytes);
+ cipher.init(Cipher.DECRYPT_MODE, aesKey, ivParamSpec);
+ return cipher.doFinal(encryptedBytes);
+ }
+
+
+ private byte[] computeAuthenticationTag(byte[] aadBytes, byte[] ivBytes, byte[] cipherBytes, Key hmacKeySpec) throws NoSuchAlgorithmException, InvalidKeyException {
+ // Compute "al"
+ ByteBuffer b = ByteBuffer.allocate(4);
+ b.order(ByteOrder.BIG_ENDIAN); // optional, the initial order of a byte buffer is always BIG_ENDIAN.
+ int aadLengthInBits = aadBytes.length * 8;
+ b.putInt(aadLengthInBits);
+ byte[] result1 = b.array();
+ byte[] al = new byte[8];
+ System.arraycopy(result1, 0, al, 4, 4);
+
+ byte[] concatenatedHmacInput = new byte[aadBytes.length + ivBytes.length + cipherBytes.length + al.length];
+ System.arraycopy(aadBytes, 0, concatenatedHmacInput, 0, aadBytes.length);
+ System.arraycopy(ivBytes, 0, concatenatedHmacInput, aadBytes.length, ivBytes.length );
+ System.arraycopy(cipherBytes, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length , cipherBytes.length);
+ System.arraycopy(al, 0, concatenatedHmacInput, aadBytes.length + ivBytes.length + cipherBytes.length, al.length);
+
+ String hmacShaAlg = getHmacShaAlgorithm();
+ Mac macImpl = Mac.getInstance(hmacShaAlg);
+ macImpl.init(hmacKeySpec);
+ macImpl.update(concatenatedHmacInput);
+ byte[] macEncoded = macImpl.doFinal();
+
+ int authTagLength = getAuthenticationTagLength();
+ return Arrays.copyOf(macEncoded, authTagLength);
+ }
+
+
+ @Override
+ public void deserializeCEK(JWEKeyStorage keyStorage) {
+ byte[] cekBytes = keyStorage.getCekBytes();
+
+ int cekLength = getExpectedCEKLength();
+ byte[] cekMacKey = Arrays.copyOf(cekBytes, cekLength / 2);
+ byte[] cekAesKey = Arrays.copyOfRange(cekBytes, cekLength / 2, cekLength);
+
+ SecretKeySpec aesKey = new SecretKeySpec(cekAesKey, "AES");
+ SecretKeySpec hmacKey = new SecretKeySpec(cekMacKey, "HMACSHA2");
+
+ keyStorage.setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION);
+ keyStorage.setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+ }
+
+
+ @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");
+ }
+
+ Key hmacShaKey = keyStorage.getCEKKey(JWEKeyStorage.KeyUse.SIGNATURE, false);
+ if (hmacShaKey == null) {
+ throw new IllegalArgumentException("HMAC CEK key not present");
+ }
+
+ byte[] hmacBytes = hmacShaKey.getEncoded();
+ byte[] aesBytes = aesKey.getEncoded();
+
+ byte[] result = new byte[hmacBytes.length + aesBytes.length];
+ System.arraycopy(hmacBytes, 0, result, 0, hmacBytes.length);
+ System.arraycopy(aesBytes, 0, result, hmacBytes.length, aesBytes.length);
+
+ return result;
+ }
+
+
+
+ public static class Aes128CbcHmacSha256Provider extends AesCbcHmacShaEncryptionProvider {
+
+ @Override
+ protected int getExpectedAesKeyLength() {
+ return 16;
+ }
+
+ @Override
+ protected String getHmacShaAlgorithm() {
+ return "HMACSHA256";
+ }
+
+ @Override
+ protected int getAuthenticationTagLength() {
+ return 16;
+ }
+
+ @Override
+ public int getExpectedCEKLength() {
+ return 32;
+ }
+ }
+
+
+ public static class Aes192CbcHmacSha384Provider extends AesCbcHmacShaEncryptionProvider {
+
+ @Override
+ protected int getExpectedAesKeyLength() {
+ return 24;
+ }
+
+ @Override
+ protected String getHmacShaAlgorithm() {
+ return "HMACSHA384";
+ }
+
+ @Override
+ protected int getAuthenticationTagLength() {
+ return 24;
+ }
+
+ @Override
+ public int getExpectedCEKLength() {
+ return 48;
+ }
+ }
+
+
+ public static class Aes256CbcHmacSha512Provider extends AesCbcHmacShaEncryptionProvider {
+
+ @Override
+ protected int getExpectedAesKeyLength() {
+ return 32;
+ }
+
+ @Override
+ protected String getHmacShaAlgorithm() {
+ return "HMACSHA512";
+ }
+
+ @Override
+ protected int getAuthenticationTagLength() {
+ return 32;
+ }
+
+ @Override
+ public int getExpectedCEKLength() {
+ return 64;
+ }
+ }
+
+
+}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java b/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java
new file mode 100644
index 0000000000..c49fe242e5
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jwe/enc/JWEEncryptionProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 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.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+
+/**
+ * @author Marek Posolda
+ */
+public interface JWEEncryptionProvider {
+
+ /**
+ * This method usually has 3 outputs:
+ * - generated initialization vector
+ * - encrypted content
+ * - authenticationTag for MAC validation
+ *
+ * It is supposed to call {@link JWE#setEncryptedContentInfo(byte[], byte[], byte[])} after it's finished
+ *
+ * @param jwe
+ * @throws IOException
+ * @throws GeneralSecurityException
+ */
+ void encodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
+
+
+ /**
+ * This method is supposed to verify checksums and decrypt content. Then it needs to call {@link JWE#content(byte[])} after it's finished
+ *
+ * @param jwe
+ * @throws IOException
+ * @throws GeneralSecurityException
+ */
+ void verifyAndDecodeJwe(JWE jwe) throws IOException, GeneralSecurityException;
+
+
+ /**
+ * This method requires that decoded CEK keys are present in the keyStorage.decodedCEK map before it's called
+ *
+ * @param keyStorage
+ * @return
+ */
+ byte[] serializeCEK(JWEKeyStorage keyStorage);
+
+ /**
+ * This method is supposed to deserialize keys. It requires that {@link JWEKeyStorage#getCekBytes()} is set. After keys are deserialized,
+ * this method needs to call {@link JWEKeyStorage#setCEKKey(Key, JWEKeyStorage.KeyUse)} according to all uses, which this encryption algorithm requires.
+ *
+ * @param keyStorage
+ */
+ void deserializeCEK(JWEKeyStorage keyStorage);
+
+ int getExpectedCEKLength();
+
+}
diff --git a/core/src/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java
new file mode 100644
index 0000000000..74b75f1b4d
--- /dev/null
+++ b/core/src/test/java/org/keycloak/jose/JWETest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017 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;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Key;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.common.util.Base64Url;
+import org.keycloak.jose.jwe.JWE;
+import org.keycloak.jose.jwe.JWEConstants;
+import org.keycloak.jose.jwe.JWEHeader;
+import org.keycloak.jose.jwe.JWEKeyStorage;
+
+/**
+ * @author Marek Posolda
+ */
+public class JWETest {
+
+ private static final String PAYLOAD = "Hello world! How are you? This is some quite a long text, which is much longer than just simple 'Hello World'";
+
+ private static final byte[] HMAC_SHA256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16 };
+ private static final byte[] AES_128_KEY = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+ private static final byte[] HMAC_SHA512_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+ private static final byte[] AES_256_KEY = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
+
+ @Test
+ public void testDirect_Aes128CbcHmacSha256() throws Exception {
+ SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+ SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+
+ JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, JWEConstants.A128CBC_HS256, null);
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(PAYLOAD.getBytes("UTF-8"));
+
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ String encodedContent = jwe.encodeJwe();
+
+ System.out.println("Encoded content: " + encodedContent);
+ System.out.println("Encoded content length: " + encodedContent.length());
+
+ jwe = new JWE();
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ jwe.verifyAndDecodeJwe(encodedContent);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+
+ }
+
+
+ @Test
+ public void testDirect_Aes256CbcHmacSha512() throws Exception {
+ final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
+ final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
+
+ JWEHeader jweHeader = new JWEHeader(JWEConstants.DIR, JWEConstants.A256CBC_HS512, null);
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(PAYLOAD.getBytes("UTF-8"));
+
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ String encodedContent = jwe.encodeJwe();
+
+ System.out.println("Encoded content: " + encodedContent);
+ System.out.println("Encoded content length: " + encodedContent.length());
+
+ jwe = new JWE();
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ jwe.verifyAndDecodeJwe(encodedContent);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+
+ }
+
+ @Test
+ public void testAesKW_Aes128CbcHmacSha256() throws Exception {
+ SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+
+ JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
+ JWE jwe = new JWE()
+ .header(jweHeader)
+ .content(PAYLOAD.getBytes("UTF-8"));
+
+ jwe.getKeyStorage()
+ .setEncryptionKey(aesKey);
+
+ String encodedContent = jwe.encodeJwe();
+
+ System.out.println("Encoded content: " + encodedContent);
+ System.out.println("Encoded content length: " + encodedContent.length());
+
+ jwe = new JWE();
+ jwe.getKeyStorage()
+ .setEncryptionKey(aesKey);
+
+ jwe.verifyAndDecodeJwe(encodedContent);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+ }
+
+
+ @Test
+ public void externalJweAes128CbcHmacSha256Test() throws UnsupportedEncodingException {
+ String externalJwe = "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..qysUrI1iVtiG4Z4jyr7XXg.apdNSQhR7WDMg6IHf5aLVI0gGp6JuOHYmIUtflns4WHmyxOOnh_GShLI6DWaK_SiywTV5gZvZYtl8H8Iv5fTfLkc4tiDDjbdtmsOP7tqyRxVh069gU5UvEAgmCXbIKALutgYXcYe2WM4E6BIHPTSt8jXdkktFcm7XHiD7mpakZyjXsG8p3XVkQJ72WbJI_t6.Ks6gHeko7BRTZ4CFs5ijRA";
+ System.out.println("External encoded content length: " + externalJwe.length());
+
+ final SecretKey aesKey = new SecretKeySpec(AES_128_KEY, "AES");
+ final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA256_KEY, "HMACSHA2");
+
+ JWE jwe = new JWE();
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ jwe.verifyAndDecodeJwe(externalJwe);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+ }
+
+
+ @Test
+ public void externalJweAes256CbcHmacSha512Test() throws UnsupportedEncodingException {
+ String externalJwe = "eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiZGlyIn0..xUPndQ5U69CYaWMKr4nyeg.AzSzba6OdNsvTIoNpub8d2TmYnkY7W8Sd-1S33DjJwJsSaNcfvfXBq5bqXAGVAnLHrLZJKWoEYsmOrYHz3Nao-kpLtUpc4XZI8yiYUqkHTjmxZnfD02R6hz31a5KBCnDTtUEv23VSxm8yUyQKoUTpVHbJ3b2VQvycg2XFUXPsA6oaSSEpz-uwe1Vmun2hUBB.Qal4rMYn1RrXQ9AQ9ONUjUXvlS2ow8np-T8QWMBR0ns";
+ System.out.println("External encoded content length: " + externalJwe.length());
+
+ final SecretKey aesKey = new SecretKeySpec(AES_256_KEY, "AES");
+ final SecretKey hmacKey = new SecretKeySpec(HMAC_SHA512_KEY, "HMACSHA2");
+
+ JWE jwe = new JWE();
+ jwe.getKeyStorage()
+ .setCEKKey(aesKey, JWEKeyStorage.KeyUse.ENCRYPTION)
+ .setCEKKey(hmacKey, JWEKeyStorage.KeyUse.SIGNATURE);
+
+ jwe.verifyAndDecodeJwe(externalJwe);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals(PAYLOAD, decodedContent);
+ }
+
+
+ @Test
+ public void externalJweAesKeyWrapTest() throws Exception {
+ // See example "A.3" from JWE specification - https://tools.ietf.org/html/rfc7516#page-41
+ String externalJwe = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ.AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.U0m_YmjN04DJvceFICbCVQ";
+
+ byte[] aesKey = Base64Url.decode("GawgguFyGrWKav7AX4VKUg");
+ SecretKeySpec aesKeySpec = new SecretKeySpec(aesKey, "AES");
+
+ JWE jwe = new JWE();
+ jwe.getKeyStorage()
+ .setEncryptionKey(aesKeySpec);
+
+ jwe.verifyAndDecodeJwe(externalJwe);
+
+ String decodedContent = new String(jwe.getContent(), "UTF-8");
+
+ Assert.assertEquals("Live long and prosper.", decodedContent);
+
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
index fa9b7db3d0..3f7d258186 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
@@ -48,16 +48,16 @@ public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
public String useConfigTemplateFromCache() {
- return useConfigTemplateFromCache==null ? null : useConfigTemplateFromCache.get();
+ return useConfigTemplateFromCache.get();
}
public String remoteServers() {
- return remoteServers==null ? null : remoteServers.get();
+ return remoteServers.get();
}
public Boolean sessionCache() {
- return sessionCache==null ? false : sessionCache.get();
+ return sessionCache.get()==null ? false : sessionCache.get();
}
}