keycloak-uncached

KEYCLOAK-5569 Added JWE

9/22/2017 6:30:17 AM

Details

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 0000000..2a6eede
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..98ab7b3
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..057f487
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..dcec260
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..c49fe24
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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/main/java/org/keycloak/jose/jwe/JWE.java b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
new file mode 100644
index 0000000..03bb43f
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..d81141d
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 0000000..30b5150
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@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 0000000..9ce042c
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWEKeyStorage {
+
+    private Key encryptionKey;
+
+    private byte[] cekBytes;
+
+    private Map<KeyUse, Key> 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 0000000..80aaea5
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class JWERegistry {
+
+    // https://tools.ietf.org/html/rfc7518#page-12
+    // Registry not pluggable for now. Just supported algorithms included
+    private static final Map<String, JWEEncryptionProvider> 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<String, JWEAlgorithmProvider> 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 0000000..d88ec56
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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/test/java/org/keycloak/jose/JWETest.java b/core/src/test/java/org/keycloak/jose/JWETest.java
new file mode 100644
index 0000000..74b75f1
--- /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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+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 fa9b7db..3f7d258 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();
     }
 }