You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2019/11/28 12:19:28 UTC

[cxf] branch master updated: [CXF-8162] JWE with multiple recipients does not work for AES CBC Encryption (#604)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new d3a968f  [CXF-8162] JWE with multiple recipients does not work for AES CBC Encryption (#604)
d3a968f is described below

commit d3a968fb0ea586ac185483a169b1c5ab29dc5615
Author: frelibert <fr...@yahoo.com>
AuthorDate: Thu Nov 28 13:19:17 2019 +0100

    [CXF-8162] JWE with multiple recipients does not work for AES CBC Encryption (#604)
---
 .../jose/jwe/AesCbcContentEncryptionAlgorithm.java |  82 +++++++++++++
 .../security/jose/jwe/AesCbcHmacJweEncryption.java |  96 ++++++---------
 .../apache/cxf/rs/security/jose/jwe/JweUtils.java  |   3 +
 .../rs/security/jose/jwe/JweJsonProducerTest.java  | 130 ++++++++++++++++++++-
 4 files changed, 247 insertions(+), 64 deletions(-)

diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcContentEncryptionAlgorithm.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcContentEncryptionAlgorithm.java
new file mode 100644
index 0000000..c87907a
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcContentEncryptionAlgorithm.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.rs.security.jose.jwe;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.spec.IvParameterSpec;
+
+import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
+import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+
+public class AesCbcContentEncryptionAlgorithm extends AbstractContentEncryptionAlgorithm {
+    
+    static final Map<String, String> AES_HMAC_MAP;
+    static final Map<String, Integer> AES_CEK_SIZE_MAP;
+    
+    static {
+        AES_HMAC_MAP = new HashMap<>();
+        AES_HMAC_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), AlgorithmUtils.HMAC_SHA_256_JAVA);
+        AES_HMAC_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), AlgorithmUtils.HMAC_SHA_384_JAVA);
+        AES_HMAC_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), AlgorithmUtils.HMAC_SHA_512_JAVA);
+
+        AES_CEK_SIZE_MAP = new HashMap<>();
+        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), 32);
+        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), 48);
+        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), 64);
+    }
+    
+    public AesCbcContentEncryptionAlgorithm(ContentAlgorithm algo, boolean generateCekOnce) {
+        super(validateCekAlgorithm(algo), generateCekOnce);
+    }
+    
+    public AesCbcContentEncryptionAlgorithm(byte[] cek, byte[] iv, ContentAlgorithm algo) {
+        super(cek, iv, validateCekAlgorithm(algo));
+    }
+    
+    @Override
+    public AlgorithmParameterSpec getAlgorithmParameterSpec(byte[] theIv) {
+        return new IvParameterSpec(theIv);
+    }
+    
+    @Override
+    public byte[] getAdditionalAuthenticationData(String headersJson, byte[] aad) {
+        return null;
+    }
+    
+    @Override
+    protected int getContentEncryptionKeySize(JweHeaders headers) {
+        return getFullCekKeySize(getAlgorithm().getJwaName()) * 8;
+    }
+    
+    protected static int getFullCekKeySize(String algoJwt) {
+        return AES_CEK_SIZE_MAP.get(algoJwt);
+    }
+    
+    protected static ContentAlgorithm validateCekAlgorithm(ContentAlgorithm cekAlgo) {
+        if (!AlgorithmUtils.isAesCbcHmac(cekAlgo.getJwaName())) {
+            LOG.warning("Invalid content encryption algorithm");
+            throw new JweException(JweException.Error.INVALID_CONTENT_ALGORITHM);
+        }
+        return cekAlgo;
+    }
+
+}
diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcHmacJweEncryption.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcHmacJweEncryption.java
index 12170c1..b995245 100644
--- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcHmacJweEncryption.java
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AesCbcHmacJweEncryption.java
@@ -19,66 +19,61 @@
 package org.apache.cxf.rs.security.jose.jwe;
 
 import java.nio.ByteBuffer;
-import java.security.spec.AlgorithmParameterSpec;
-import java.util.HashMap;
-import java.util.Map;
 
 import javax.crypto.Mac;
-import javax.crypto.spec.IvParameterSpec;
 
-import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
 import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
+import org.apache.cxf.rs.security.jose.jwe.JweException.Error;
 import org.apache.cxf.rt.security.crypto.HmacUtils;
 
 public class AesCbcHmacJweEncryption extends JweEncryption {
-    private static final Map<String, String> AES_HMAC_MAP;
-    private static final Map<String, Integer> AES_CEK_SIZE_MAP;
-    static {
-        AES_HMAC_MAP = new HashMap<>();
-        AES_HMAC_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), AlgorithmUtils.HMAC_SHA_256_JAVA);
-        AES_HMAC_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), AlgorithmUtils.HMAC_SHA_384_JAVA);
-        AES_HMAC_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), AlgorithmUtils.HMAC_SHA_512_JAVA);
-
-        AES_CEK_SIZE_MAP = new HashMap<>();
-        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A128CBC_HS256.getJwaName(), 32);
-        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A192CBC_HS384.getJwaName(), 48);
-        AES_CEK_SIZE_MAP.put(ContentAlgorithm.A256CBC_HS512.getJwaName(), 64);
-    }
+    
     public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt,
                                    KeyEncryptionProvider keyEncryptionAlgorithm) {
         this(cekAlgoJwt, keyEncryptionAlgorithm, false);
     }
+    
     public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt,
                                    KeyEncryptionProvider keyEncryptionAlgorithm,
                                    boolean generateCekOnce) {
-        super(keyEncryptionAlgorithm,
-              new AesCbcContentEncryptionAlgorithm(validateCekAlgorithm(cekAlgoJwt),
-                                                   generateCekOnce));
+        super(keyEncryptionAlgorithm, new AesCbcContentEncryptionAlgorithm(cekAlgoJwt, generateCekOnce));
     }
+    
     public AesCbcHmacJweEncryption(ContentAlgorithm cekAlgoJwt, byte[] cek,
                                    byte[] iv, KeyEncryptionProvider keyEncryptionAlgorithm) {
-        super(keyEncryptionAlgorithm,
-              new AesCbcContentEncryptionAlgorithm(cek, iv,
-                                                   validateCekAlgorithm(cekAlgoJwt)));
-
+        super(keyEncryptionAlgorithm, new AesCbcContentEncryptionAlgorithm(cek, iv, cekAlgoJwt));
     }
+    
+    public AesCbcHmacJweEncryption(KeyEncryptionProvider keyEncryptionAlgorithm, 
+        AesCbcContentEncryptionAlgorithm contentEncryptionAlgorithm) {
+        super(keyEncryptionAlgorithm, contentEncryptionAlgorithm);
+    }
+    
     @Override
     protected byte[] getActualCek(byte[] theCek, String algoJwt) {
         return doGetActualCek(theCek, algoJwt);
     }
+    
     protected static byte[] doGetActualCek(byte[] theCek, String algoJwt) {
-        int size = getFullCekKeySize(algoJwt) / 2;
-        byte[] actualCek = new byte[size];
-        System.arraycopy(theCek, size, actualCek, 0, size);
-        return actualCek;
+        // K
+        int inputKeySize = AesCbcContentEncryptionAlgorithm.getFullCekKeySize(algoJwt);
+        if (theCek.length != inputKeySize) {
+            LOG.warning("Length input key [" + theCek.length + "] invalid for algorithm " + algoJwt 
+                + " [" + inputKeySize + "]");
+            throw new JweException(Error.INVALID_CONTENT_KEY); 
+        }
+        // MAC_KEY, ENC_KEY
+        int secondaryKeySize = inputKeySize / 2;
+        // Extract secondary key ENC_KEY from the input key K
+        byte[] encKey = new byte[secondaryKeySize];
+        System.arraycopy(theCek, secondaryKeySize, encKey, 0, secondaryKeySize);
+        return encKey;
     }
 
-    protected static int getFullCekKeySize(String algoJwt) {
-        return AES_CEK_SIZE_MAP.get(algoJwt);
-    }
     protected byte[] getActualCipher(byte[] cipher) {
         return cipher;
     }
+    
     protected byte[] getAuthenticationTag(JweEncryptionInternal state, byte[] cipher) {
         final MacState macState = getInitializedMacState(state);
         macState.mac.update(cipher);
@@ -94,21 +89,23 @@ public class AesCbcHmacJweEncryption extends JweEncryption {
         System.arraycopy(sig, 0, authTag, 0, authTagLen);
         return authTag;
     }
+    
     private MacState getInitializedMacState(final JweEncryptionInternal state) {
         return getInitializedMacState(state.secretKey, state.theIv, state.aad,
                                       state.theHeaders, state.protectedHeadersJson);
     }
+    
     protected static MacState getInitializedMacState(byte[] secretKey,
                                                      byte[] theIv,
                                                      byte[] extraAad,
                                                      JweHeaders theHeaders,
                                                      String protectedHeadersJson) {
         String algoJwt = theHeaders.getContentEncryptionAlgorithm().getJwaName();
-        int size = getFullCekKeySize(algoJwt) / 2;
+        int size = AesCbcContentEncryptionAlgorithm.getFullCekKeySize(algoJwt) / 2;
         byte[] macKey = new byte[size];
         System.arraycopy(secretKey, 0, macKey, 0, size);
 
-        String hmacAlgoJava = AES_HMAC_MAP.get(algoJwt);
+        String hmacAlgoJava = AesCbcContentEncryptionAlgorithm.AES_HMAC_MAP.get(algoJwt);
         Mac mac = HmacUtils.getInitializedMac(macKey, hmacAlgoJava, null);
 
         byte[] aad = JweUtils.getAdditionalAuthenticationData(protectedHeadersJson, extraAad);
@@ -140,42 +137,15 @@ public class AesCbcHmacJweEncryption extends JweEncryption {
             }
         };
     }
+
     @Override
     protected byte[] getEncryptedContentEncryptionKey(JweHeaders headers, byte[] theCek) {
         return getKeyEncryptionAlgo().getEncryptedContentEncryptionKey(headers, theCek);
     }
 
-    private static class AesCbcContentEncryptionAlgorithm extends AbstractContentEncryptionAlgorithm {
-        AesCbcContentEncryptionAlgorithm(ContentAlgorithm algo, boolean generateCekOnce) {
-            super(algo, generateCekOnce);
-        }
-        AesCbcContentEncryptionAlgorithm(byte[] cek, byte[] iv, ContentAlgorithm algo) {
-            super(cek, iv, algo);
-        }
-        @Override
-        public AlgorithmParameterSpec getAlgorithmParameterSpec(byte[] theIv) {
-            return new IvParameterSpec(theIv);
-        }
-        @Override
-        public byte[] getAdditionalAuthenticationData(String headersJson, byte[] aad) {
-            return null;
-        }
-        @Override
-        protected int getContentEncryptionKeySize(JweHeaders headers) {
-            return getFullCekKeySize(getAlgorithm().getJwaName()) * 8;
-        }
-    }
-
     protected static class MacState {
         protected Mac mac;
         private byte[] al;
     }
-
-    private static ContentAlgorithm validateCekAlgorithm(ContentAlgorithm cekAlgo) {
-        if (!AlgorithmUtils.isAesCbcHmac(cekAlgo.getJwaName())) {
-            LOG.warning("Invalid content encryption algorithm");
-            throw new JweException(JweException.Error.INVALID_CONTENT_ALGORITHM);
-        }
-        return cekAlgo;
-    }
+    
 }
diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java
index b2b91e0..32696b0 100644
--- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java
@@ -267,6 +267,9 @@ public final class JweUtils {
         if (AlgorithmUtils.isAesGcm(algorithm.getJwaName())) {
             return new AesGcmContentEncryptionAlgorithm(key, null, algorithm);
         }
+        if (AlgorithmUtils.isAesCbcHmac(algorithm.getJwaName())) {
+            return new AesCbcContentEncryptionAlgorithm(key, null, algorithm);
+        }
         return null;
     }
     public static ContentEncryptionProvider getContentEncryptionProvider(ContentAlgorithm algorithm) {
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducerTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducerTest.java
index 6d438e2..c1a2f35 100644
--- a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducerTest.java
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducerTest.java
@@ -19,6 +19,7 @@
 package org.apache.cxf.rs.security.jose.jwe;
 
 import java.security.Security;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -32,8 +33,10 @@ import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm;
 import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm;
 import org.apache.cxf.rt.security.crypto.CryptoUtils;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -43,6 +46,7 @@ public class JweJsonProducerTest {
     static final byte[] WRAPPER_BYTES1 = {91, 96, 105, 38, 99, 108, 110, 8, -93, 50, -15, 62, 0, -115, 73, -39};
     static final byte[] WRAPPER_BYTES2 = {-39, 96, 105, 38, 99, 108, 110, 8, -93, 50, -15, 62, 0, -115, 73, 91};
     static final byte[] CEK_BYTES = {-43, 123, 77, 115, 40, 49, -4, -9, -48, -74, 62, 59, 60, 102, -22, -100};
+    static final String CEK_32_HEX = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
     static final String SINGLE_RECIPIENT_OUTPUT =
         "{"
         + "\"protected\":\"eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIn0\","
@@ -148,6 +152,30 @@ public class JweJsonProducerTest {
         + "\"ciphertext\":\"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY\","
         + "\"tag\":\"Mz-VPPyU4RlcuYv1IwIvzw\""
         + "}";
+    static final String MULTIPLE_RECIPIENTS_A128CBCHS256_JSON_OUTPUT = 
+        "{"
+        + "\"protected\":\"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0\","
+        + "\"unprotected\":"
+        + "{"
+        + "\"jku\":\"https://server.example.com/keys.jwks\","
+        + "\"alg\":\"A128KW\""
+        + "},"
+        + "\"recipients\":["
+        + "{"
+        + "\"header\":{\"kid\":\"key1\"},"
+        + "\"encrypted_key\":\"NrhRVDNccP-gul5SB393C8DlbEtgGdvSgYgH5QRUXJYt-r8_wzef1g\""
+        + "},{"
+        + "\"header\":{\"kid\":\"key2\"},"
+        + "\"encrypted_key\":\"6a_nnEYO45qB_Vp6N2QbFQ7Cv1uecbiE\""
+        + "}],"
+        + "\"aad\":\"WyJ2Y2FyZCIsW1sidmVyc2lvbiIse30sInRleHQiLCI0LjAiXSxbImZuIix7fSwidGV4dCIsIk1lcmlhZG9jIEJyYW5ke"
+        + "WJ1Y2siXSxbIm4iLHt9LCJ0ZXh0IixbIkJyYW5keWJ1Y2siLCJNZXJpYWRvYyIsIk1yLiIsIiJdXSxbImJkYXkiLHt9LCJ0ZXh0Iiwi"
+        + "VEEgMjk4MiJdLFsiZ2VuZGVyIix7fSwidGV4dCIsIk0iXV1d\","
+        + "\"iv\":\"AxY8DCtDaGlsbGljb3RoZQ\","
+        + "\"ciphertext\":\"pwitNt2DsK1zM72z5CxGClCr8ANYuIYZgCnohazsPyZhvR8atJnnlkR3fdSpyXYJcNx2LP-gcm3oNWiaAk0H2A\","
+        + "\"tag\":\"nNSN9kYhubsQ9QELBmZIhA\""
+        + "}";
+    
     @BeforeClass
     public static void registerBouncyCastleIfNeeded() throws Exception {
         try {
@@ -270,7 +298,7 @@ public class JweJsonProducerTest {
         assertEquals(SINGLE_RECIPIENT_ALL_HEADERS_AAD_OUTPUT, jweJson);
     }
     @Test
-    public void testMultipleRecipients() {
+    public void testMultipleRecipientsA128GCM() {
         final String text = "The true sign of intelligence is not knowledge but imagination.";
         SecretKey wrapperKey1 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES1, "AES");
         SecretKey wrapperKey2 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES2, "AES");
@@ -308,4 +336,104 @@ public class JweJsonProducerTest {
         String jweJson = p.encryptWith(jweProviders, perRecipientHeades);
         assertEquals(MULTIPLE_RECIPIENTS_OUTPUT, jweJson);
     }
+    
+    @Test
+    public void testMultipleRecipientsA128CBCHS256GivenCek() throws Exception {
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        
+        KeyAlgorithm keyAlgo = KeyAlgorithm.A128KW;
+        ContentAlgorithm contentAlgo = ContentAlgorithm.A128CBC_HS256;
+        
+        SecretKey wrapperKey1 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES1, "AES");
+        SecretKey wrapperKey2 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES2, "AES");
+
+        JweHeaders protectedHeaders = new JweHeaders(contentAlgo);
+        JweHeaders sharedUnprotectedHeaders = new JweHeaders();
+        sharedUnprotectedHeaders.setJsonWebKeysUrl("https://server.example.com/keys.jwks");
+        
+        sharedUnprotectedHeaders.setKeyEncryptionAlgorithm(keyAlgo);
+
+        List<JweEncryptionProvider> jweProviders = new LinkedList<>();
+
+        KeyEncryptionProvider keyEncryption1 =
+            JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey1, keyAlgo);
+        
+        JweEncryptionProvider jwe1 = new AesCbcHmacJweEncryption(contentAlgo, Hex.decode(CEK_32_HEX), 
+            JweCompactReaderWriterTest.INIT_VECTOR_A3, keyEncryption1);
+        KeyEncryptionProvider keyEncryption2 =
+            JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey2, keyAlgo);
+        JweEncryptionProvider jwe2 = new AesCbcHmacJweEncryption(contentAlgo, CEK_BYTES, 
+            JweCompactReaderWriterTest.INIT_VECTOR_A3, keyEncryption2);
+        jweProviders.add(jwe1);
+        jweProviders.add(jwe2);
+
+        List<JweHeaders> perRecipientHeades = new LinkedList<>();
+        perRecipientHeades.add(new JweHeaders("key1"));
+        perRecipientHeades.add(new JweHeaders("key2"));
+
+        JweJsonProducer p = new JweJsonProducer(protectedHeaders,
+                                                sharedUnprotectedHeaders,
+                                                StringUtils.toBytesUTF8(text),
+                                                StringUtils.toBytesUTF8(EXTRA_AAD_SOURCE),
+                                                false);
+
+        String jweJson = p.encryptWith(jweProviders, perRecipientHeades);
+        assertEquals(MULTIPLE_RECIPIENTS_A128CBCHS256_JSON_OUTPUT, jweJson);
+    }
+    
+    @Test
+    public void testMultipleRecipientsA128CBCHS256() {
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        
+        KeyAlgorithm keyAlgo = KeyAlgorithm.A128KW;
+        ContentAlgorithm contentAlgo = ContentAlgorithm.A128CBC_HS256;
+        
+        SecretKey wrapperKey1 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES1, "AES");
+        SecretKey wrapperKey2 = CryptoUtils.createSecretKeySpec(WRAPPER_BYTES2, "AES");
+
+        JweHeaders protectedHeaders = new JweHeaders(contentAlgo);
+        JweHeaders sharedUnprotectedHeaders = new JweHeaders();
+        sharedUnprotectedHeaders.setJsonWebKeysUrl("https://server.example.com/keys.jwks");
+        
+        sharedUnprotectedHeaders.setKeyEncryptionAlgorithm(keyAlgo);
+
+        List<JweEncryptionProvider> jweProviders = new LinkedList<>();
+
+        AesCbcContentEncryptionAlgorithm contentEncryption = new AesCbcContentEncryptionAlgorithm(contentAlgo, true);
+        
+        KeyEncryptionProvider keyEncryption1 = JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey1, keyAlgo);
+        JweEncryptionProvider jwe1 = new AesCbcHmacJweEncryption(keyEncryption1, contentEncryption);
+        KeyEncryptionProvider keyEncryption2 = JweUtils.getSecretKeyEncryptionAlgorithm(wrapperKey2, keyAlgo);
+        JweEncryptionProvider jwe2 = new AesCbcHmacJweEncryption(keyEncryption2, contentEncryption);
+        
+        jweProviders.add(jwe1);
+        jweProviders.add(jwe2);
+
+        List<JweHeaders> perRecipientHeades = new LinkedList<>();
+        perRecipientHeades.add(new JweHeaders("key1"));
+        perRecipientHeades.add(new JweHeaders("key2"));
+
+        JweJsonProducer p = new JweJsonProducer(protectedHeaders,
+                                                sharedUnprotectedHeaders,
+                                                StringUtils.toBytesUTF8(text),
+                                                StringUtils.toBytesUTF8(EXTRA_AAD_SOURCE),
+                                                false);
+
+        String jweJson = p.encryptWith(jweProviders, perRecipientHeades);
+        
+        JweJsonConsumer consumer = new JweJsonConsumer(jweJson);
+        Assert.assertEquals(keyAlgo, consumer.getSharedUnprotectedHeader().getKeyEncryptionAlgorithm());
+        Assert.assertEquals(contentAlgo, consumer.getProtectedHeader().getContentEncryptionAlgorithm());
+        
+        // Recipient 1
+        JweDecryptionProvider jwd1 = JweUtils.createJweDecryptionProvider(wrapperKey1, keyAlgo, contentAlgo);
+        JweDecryptionOutput out1 = consumer.decryptWith(jwd1, Collections.singletonMap("kid", "key1"));
+        assertEquals(text, out1.getContentText());
+        // Recipient 2
+        JweDecryptionProvider jwd2 = JweUtils.createJweDecryptionProvider(wrapperKey2, keyAlgo, contentAlgo);
+
+        JweDecryptionOutput out2 = consumer.decryptWith(jwd2, Collections.singletonMap("kid", "key2"));
+        assertEquals(text, out2.getContentText());
+    }
+    
 }