cxf-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cohei...@apache.org
Subject [11/20] cxf git commit: Renaming jose module
Date Wed, 21 Oct 2015 15:13:07 GMT
http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtTokenReaderWriter.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtTokenReaderWriter.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtTokenReaderWriter.java
new file mode 100644
index 0000000..fba861e
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtTokenReaderWriter.java
@@ -0,0 +1,55 @@
+/**
+ * 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.jwt;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
+
+
+
+
+public class JwtTokenReaderWriter extends JsonMapObjectReaderWriter {
+    private static final Set<String> DATE_PROPERTIES = 
+        new HashSet<String>(Arrays.asList(JwtConstants.CLAIM_EXPIRY, 
+                                          JwtConstants.CLAIM_ISSUED_AT, 
+                                          JwtConstants.CLAIM_NOT_BEFORE));
+
+    public String claimsToJson(JwtClaims claims) {
+        return toJson(claims);
+    }
+
+    public JwtClaims fromJsonClaims(String claimsJson) {
+        JwtClaims claims = new JwtClaims();
+        fromJson(claims, claimsJson);
+        return claims;
+        
+    }
+    
+    @Override
+    protected Object readPrimitiveValue(String name, String json, int from, int to) {
+        Object value = super.readPrimitiveValue(name, json, from, to);
+        if (DATE_PROPERTIES.contains(name)) {
+            value = Long.valueOf(value.toString());
+        }
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java
new file mode 100644
index 0000000..3f0a27e
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java
@@ -0,0 +1,107 @@
+/**
+ * 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.jwt;
+
+import java.util.Date;
+
+public final class JwtUtils {
+    private JwtUtils() {
+        
+    }
+    public static String claimsToJson(JwtClaims claims) {
+        return claimsToJson(claims, null);
+    }
+    public static String claimsToJson(JwtClaims claims, JwtTokenReaderWriter writer) {
+        if (writer == null) {
+            writer = new JwtTokenReaderWriter();
+        }
+        return writer.claimsToJson(claims);
+    }
+    public static JwtClaims jsonToClaims(String json) {
+        return new JwtTokenReaderWriter().fromJsonClaims(json);
+    }
+    
+    public static void validateJwtExpiry(JwtClaims claims, int clockOffset, boolean claimRequired) {
+        Long expiryTime = claims.getExpiryTime();
+        if (expiryTime == null) {
+            if (claimRequired) {
+                throw new JwtException("The token has expired");
+            }
+            return;
+        }
+        Date rightNow = new Date();
+        Date expiresDate = new Date(expiryTime * 1000L);
+        if (clockOffset != 0) {
+            expiresDate.setTime(expiresDate.getTime() + (long)clockOffset * 1000L);
+        }
+        if (expiresDate.before(rightNow)) {
+            throw new JwtException("The token has expired");
+        }
+    }
+    
+    public static void validateJwtNotBefore(JwtClaims claims, int clockOffset, boolean claimRequired) {
+        Long notBeforeTime = claims.getNotBefore();
+        if (notBeforeTime == null) {
+            if (claimRequired) {
+                throw new JwtException("The token cannot be accepted yet");
+            }
+            return;
+        }
+        
+        Date validCreation = new Date();
+        long currentTime = validCreation.getTime();
+        if (clockOffset != 0) {
+            validCreation.setTime(currentTime + (long)clockOffset * 1000L);
+        }
+        Date notBeforeDate = new Date(notBeforeTime * 1000L);
+
+        // Check to see if the not before time is in the future
+        if (notBeforeDate.after(validCreation)) {
+            throw new JwtException("The token cannot be accepted yet");
+        }
+    }
+    
+    public static void validateJwtIssuedAt(JwtClaims claims, int timeToLive, int clockOffset, boolean claimRequired) {
+        Long issuedAtInSecs = claims.getIssuedAt();
+        if (issuedAtInSecs == null) {
+            if (claimRequired) {
+                throw new JwtException("Invalid issuedAt");
+            }
+            return;
+        }
+        
+        Date createdDate = new Date(issuedAtInSecs * 1000L);
+        if (clockOffset != 0) {
+            // Calculate the time that is allowed for the message to travel
+            createdDate.setTime(createdDate.getTime() - (long)clockOffset * 1000L);
+        }
+        
+        Date validCreation = new Date();
+        if (timeToLive != 0) {
+            long currentTime = validCreation.getTime();
+            currentTime -= (long)timeToLive * 1000L;
+            validCreation.setTime(currentTime);
+        }
+        
+        if (createdDate.after(validCreation)) {
+            throw new JwtException("Invalid issuedAt");
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwkJoseCookBookTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwkJoseCookBookTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwkJoseCookBookTest.java
new file mode 100644
index 0000000..b9cb8bf
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwkJoseCookBookTest.java
@@ -0,0 +1,187 @@
+/**
+ * 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.cookbook;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
+import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
+import org.apache.cxf.rs.security.jose.jwk.KeyType;
+import org.apache.cxf.rs.security.jose.jwk.PublicKeyUse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JwkJoseCookBookTest extends Assert {
+
+    private static final String EC_X_COORDINATE_VALUE = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9"
+        + "A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt";
+    private static final String EC_Y_COORDINATE_VALUE = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy"
+        + "SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1";
+    private static final String EC_KID_VALUE = "bilbo.baggins@hobbiton.example";
+    private static final String EC_CURVE_VALUE = "P-521";
+    private static final String EC_PRIVATE_KEY_VALUE = "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb"
+        + "KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt";
+    private static final String RSA_MODULUS_VALUE = "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT"
+        + "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV"
+        + "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-"
+        + "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde"
+        + "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC"
+        + "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g"
+        + "HdrNP5zw";
+    private static final String RSA_PUBLIC_EXP_VALUE = "AQAB";
+    private static final String RSA_KID_VALUE = "bilbo.baggins@hobbiton.example";
+    private static final String RSA_PRIVATE_EXP_VALUE = "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e"
+        + "iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld"
+        + "Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b"
+        + "MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU"
+        + "6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj"
+        + "d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc"
+        + "OpBrQzwQ";
+    private static final String RSA_FIRST_PRIME_FACTOR_VALUE = "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR"
+        + "aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG"
+        + "peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8"
+        + "bUq0k";
+    private static final String RSA_SECOND_PRIME_FACTOR_VALUE = "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT"
+        + "8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an"
+        + "V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0"
+        + "s7pFc";
+    private static final String RSA_FIRST_PRIME_CRT_VALUE = "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q"
+        + "1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn"
+        + "-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX"
+        + "59ehik";
+    private static final String RSA_SECOND_PRIME_CRT_VALUE = "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr"
+        + "AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK"
+        + "bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK"
+        + "T1cYF8";
+    private static final String RSA_FIRST_CRT_COEFFICIENT_VALUE =
+          "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N"
+        + "ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh"
+        + "jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP"
+        + "z8aaI4";
+    private static final String SIGN_SECRET_VALUE = "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg";
+    private static final String SIGN_KID_VALUE = "018c0ae5-4d9b-471b-bfd6-eef314bc7037";
+    private static final String ENCRYPTION_SECRET_VALUE = "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8";
+    private static final String ENCRYPTION_KID_VALUE = "1e571774-2e08-40da-8308-e8d68773842d";
+    @Test
+    public void testPublicSetAsList() throws Exception {
+        JsonWebKeys jwks = readKeySet("cookbookPublicSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        assertEquals(2, keys.size());
+        JsonWebKey ecKey = keys.get(0);
+        assertEquals(6, ecKey.asMap().size());
+        validatePublicEcKey(ecKey);
+        JsonWebKey rsaKey = keys.get(1);
+        assertEquals(5, rsaKey.asMap().size());
+        validatePublicRsaKey(rsaKey);
+    }
+    @Test
+    public void testPublicSetAsMap() throws Exception {
+        JsonWebKeys jwks = readKeySet("cookbookPublicSet.txt");
+        Map<KeyType, List<JsonWebKey>> keysMap = jwks.getKeyTypeMap();
+        assertEquals(2, keysMap.size());
+        List<JsonWebKey> rsaKeys = keysMap.get(KeyType.RSA);
+        assertEquals(1, rsaKeys.size());
+        assertEquals(5, rsaKeys.get(0).asMap().size());
+        validatePublicRsaKey(rsaKeys.get(0));
+        List<JsonWebKey> ecKeys = keysMap.get(KeyType.EC);
+        assertEquals(1, ecKeys.size());
+        assertEquals(6, ecKeys.get(0).asMap().size());
+        validatePublicEcKey(ecKeys.get(0));
+    }
+    @Test
+    public void testPrivateSetAsList() throws Exception {
+        JsonWebKeys jwks = readKeySet("cookbookPrivateSet.txt");
+        validatePrivateSet(jwks);
+    }
+    private void validatePrivateSet(JsonWebKeys jwks) throws Exception {
+        List<JsonWebKey> keys = jwks.getKeys();
+        assertEquals(2, keys.size());
+        JsonWebKey ecKey = keys.get(0);
+        assertEquals(7, ecKey.asMap().size());
+        validatePrivateEcKey(ecKey);
+        JsonWebKey rsaKey = keys.get(1);
+        assertEquals(11, rsaKey.asMap().size());
+        validatePrivateRsaKey(rsaKey);
+    }
+    @Test
+    public void testSecretSetAsList() throws Exception {
+        JsonWebKeys jwks = readKeySet("cookbookSecretSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        assertEquals(2, keys.size());
+        JsonWebKey signKey = keys.get(0);
+        assertEquals(5, signKey.asMap().size());
+        validateSecretSignKey(signKey);
+        JsonWebKey encKey = keys.get(1);
+        assertEquals(5, encKey.asMap().size());
+        validateSecretEncKey(encKey);
+    }
+    private void validateSecretSignKey(JsonWebKey key) {
+        assertEquals(SIGN_SECRET_VALUE, key.getProperty(JsonWebKey.OCTET_KEY_VALUE));
+        assertEquals(SIGN_KID_VALUE, key.getKeyId());
+        assertEquals(KeyType.OCTET, key.getKeyType());
+        assertEquals(AlgorithmUtils.HMAC_SHA_256_ALGO, key.getAlgorithm());
+    }
+    private void validateSecretEncKey(JsonWebKey key) {
+        assertEquals(ENCRYPTION_SECRET_VALUE, key.getProperty(JsonWebKey.OCTET_KEY_VALUE));
+        assertEquals(ENCRYPTION_KID_VALUE, key.getKeyId());
+        assertEquals(KeyType.OCTET, key.getKeyType());
+        assertEquals(AlgorithmUtils.A256GCM_ALGO, key.getAlgorithm());
+    }
+    private void validatePublicRsaKey(JsonWebKey key) {
+        assertEquals(RSA_MODULUS_VALUE, key.getProperty(JsonWebKey.RSA_MODULUS));
+        assertEquals(RSA_PUBLIC_EXP_VALUE, key.getProperty(JsonWebKey.RSA_PUBLIC_EXP));
+        assertEquals(RSA_KID_VALUE, key.getKeyId());
+        assertEquals(KeyType.RSA, key.getKeyType());
+    }
+    private void validatePrivateRsaKey(JsonWebKey key) {
+        validatePublicRsaKey(key);
+        assertEquals(RSA_PRIVATE_EXP_VALUE, key.getProperty(JsonWebKey.RSA_PRIVATE_EXP));
+        assertEquals(RSA_FIRST_PRIME_FACTOR_VALUE, key.getProperty(JsonWebKey.RSA_FIRST_PRIME_FACTOR));
+        assertEquals(RSA_SECOND_PRIME_FACTOR_VALUE, key.getProperty(JsonWebKey.RSA_SECOND_PRIME_FACTOR));
+        assertEquals(RSA_FIRST_PRIME_CRT_VALUE, key.getProperty(JsonWebKey.RSA_FIRST_PRIME_CRT));
+        assertEquals(RSA_SECOND_PRIME_CRT_VALUE, key.getProperty(JsonWebKey.RSA_SECOND_PRIME_CRT));
+        assertEquals(RSA_FIRST_CRT_COEFFICIENT_VALUE, key.getProperty(JsonWebKey.RSA_FIRST_CRT_COEFFICIENT));
+    }
+    private void validatePublicEcKey(JsonWebKey key) {
+        assertEquals(EC_X_COORDINATE_VALUE, key.getProperty(JsonWebKey.EC_X_COORDINATE));
+        assertEquals(EC_Y_COORDINATE_VALUE, key.getProperty(JsonWebKey.EC_Y_COORDINATE));
+        assertEquals(EC_KID_VALUE, key.getKeyId());
+        assertEquals(KeyType.EC, key.getKeyType());
+        assertEquals(EC_CURVE_VALUE, key.getProperty(JsonWebKey.EC_CURVE));
+        assertEquals(PublicKeyUse.SIGN, key.getPublicKeyUse());
+    }
+    private void validatePrivateEcKey(JsonWebKey key) {
+        validatePublicEcKey(key);
+        assertEquals(EC_PRIVATE_KEY_VALUE, key.getProperty(JsonWebKey.EC_PRIVATE_KEY));
+    }
+    public JsonWebKeys readKeySet(String fileName) throws Exception {
+        InputStream is = JwkJoseCookBookTest.class.getResourceAsStream(fileName);
+        String s = IOUtils.readStringFromStream(is);
+        return JwkUtils.readJwkSet(s);
+    }
+    public JsonWebKey readKey(String key) throws Exception {
+        return JwkUtils.readJwkKey(key);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwsJoseCookBookTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwsJoseCookBookTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwsJoseCookBookTest.java
new file mode 100644
index 0000000..3902cf7
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/JwsJoseCookBookTest.java
@@ -0,0 +1,665 @@
+/**
+ * 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.cookbook;
+
+import java.io.InputStream;
+import java.security.Security;
+import java.util.List;
+
+import javax.crypto.Cipher;
+
+import org.apache.cxf.common.util.Base64UrlUtility;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
+import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys;
+import org.apache.cxf.rs.security.jose.jwk.JwkUtils;
+import org.apache.cxf.rs.security.jose.jws.EcDsaJwsSignatureProvider;
+import org.apache.cxf.rs.security.jose.jws.JwsCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsCompactProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJsonConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsJsonProducer;
+import org.apache.cxf.rs.security.jose.jws.JwsUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class JwsJoseCookBookTest {
+    private static final String PAYLOAD = "It’s a dangerous business, Frodo, going out your door. "
+        + "You step onto the road, and if you don't keep your feet, "
+        + "there’s no knowing where you might be swept off to.";
+    private static final String ENCODED_PAYLOAD = "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH"
+        + "lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk"
+        + "b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm"
+        + "UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4";
+    private static final String RSA_KID_VALUE = "bilbo.baggins@hobbiton.example";
+    private static final String RSA_V1_5_SIGNATURE_PROTECTED_HEADER =
+          "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX"
+        + "hhbXBsZSJ9";
+    private static final String RSA_V1_5_SIGNATURE_PROTECTED_HEADER_JSON = ("{"
+        + "\"alg\": \"RS256\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "}").replaceAll(" ", "");
+    private static final String RSA_V1_5_SIGNATURE_VALUE =
+          "MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK"
+        + "ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J"
+        + "IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w"
+        + "W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP"
+        + "xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f"
+        + "cIe8u9ipH84ogoree7vjbU5y18kDquDg";
+    private static final String RSA_V1_5_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2"
+        + "dpbnNAaG9iYml0b24uZXhhbXBsZSJ9\","
+        + "\"signature\": \"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHo"
+        + "xnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII"
+        + "7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0Rnlt"
+        + "uYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPo"
+        + "cSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxU"
+        + "Ahb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJush"
+        + "Z41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg\""
+        + "}"
+        + "]"
+        + "}").replaceAll(" ", "");
+    private static final String RSA_V1_5_JSON_FLATTENED_SERIALIZATION =  ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"protected\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbn"
+        + "NAaG9iYml0b24uZXhhbXBsZSJ9\","
+        + "\"signature\": \"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2"
+        + "e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84w"
+        + "nB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_q"
+        + "HRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9U"
+        + "zpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0"
+        + "KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogore"
+        + "e7vjbU5y18kDquDg\""
+        + "}").replaceAll(" ", "");
+    private static final String RSA_PSS_SIGNATURE_PROTECTED_HEADER_JSON = ("{"
+        + "\"alg\": \"PS384\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "}").replaceAll(" ", "");
+    private static final String RSA_PSS_SIGNATURE_PROTECTED_HEADER =
+          "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX"
+        + "hhbXBsZSJ9";
+    private static final String RSA_PSS_SIGNATURE_VALUE =
+          "cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I"
+        + "pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU"
+        + "vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX"
+        + "e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT"
+        + "0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a"
+        + "6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw";
+    private static final String RSA_PSS_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2"
+        + "dpbnNAaG9iYml0b24uZXhhbXBsZSJ9\","
+        + "\"signature\": \"cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy"
+        + "42miAh2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5"
+        + "dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz2"
+        + "8zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vd"
+        + "z0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0q"
+        + "I0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uT"
+        + "OcbH510a6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw\""
+        + "}"
+        + "]"
+        + "}").replace(" ", "");
+    private static final String RSA_PSS_JSON_FLATTENED_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"protected\": \"eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbn"
+        + "NAaG9iYml0b24uZXhhbXBsZSJ9\","
+        + "\"signature\": \"cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42mi"
+        + "Ah2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllV"
+        + "o6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf"
+        + "8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vdz0TTrxUeT3lm8d9s"
+        + "hnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aCN_2_jLAeQT"
+        + "lqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6oD"
+        + "4ifKo8DYM-X72Eaw\""
+        + "}").replace(" ", "");
+    private static final String ECDSA_KID_VALUE = "bilbo.baggins@hobbiton.example";
+    private static final String ECDSA_SIGNATURE_PROTECTED_HEADER_JSON = ("{"
+        + "\"alg\": \"ES512\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "}").replace(" ", "");
+    private static final String ECSDA_SIGNATURE_PROTECTED_HEADER =
+              "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX"
+            + "hhbXBsZSJ9";
+    private static final String HMAC_KID_VALUE = "018c0ae5-4d9b-471b-bfd6-eef314bc7037";
+    private static final String HMAC_SIGNATURE_PROTECTED_HEADER_JSON = ("{"
+        + "\"alg\": \"HS256\","
+        + "\"kid\": \"018c0ae5-4d9b-471b-bfd6-eef314bc7037\""
+        + "}").replaceAll(" ", "");
+    private static final String HMAC_SIGNATURE_PROTECTED_HEADER =
+          "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW"
+        + "VlZjMxNGJjNzAzNyJ9";
+    private static final String HMAC_SIGNATURE_VALUE = "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0";
+    private static final String HMAC_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT"
+        + "RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p"
+        + "0\""
+        + "}"
+        + "]"
+        + "}").replaceAll(" ", "");
+    private static final String HMAC_JSON_FLATTENED_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW"
+        + "ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0\""
+        + "}").replaceAll(" ", "");
+    private static final String DETACHED_HMAC_JWS =
+          ("eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW"
+        + "VlZjMxNGJjNzAzNyJ9"
+        + "."
+        + "."
+        + "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0").replaceAll(" ", "");
+    private static final String HMAC_DETACHED_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT"
+        + "RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p"
+        + "0\""
+        + "}"
+        + "]"
+        + "}").replaceAll(" ", "");
+    private static final String HMAC_DETACHED_JSON_FLATTENED_SERIALIZATION = ("{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW"
+        + "ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0\""
+        + "}").replaceAll(" ", "");
+    private static final String PROTECTING_SPECIFIC_HEADER_FIELDS_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiJ9\","
+        + "\"header\": {"
+        + "\"kid\": \"018c0ae5-4d9b-471b-bfd6-eef314bc7037\""
+        + "},"
+        + "\"signature\": \"bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr2"
+        + "0\""
+        + "}"
+        + "]"
+        + "}").replace(" ", "");
+    private static final String PROTECTING_SPECIFIC_HEADER_FIELDS_JSON_FLATTENED_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiJ9\","
+        + "\"header\": {"
+        + "\"kid\": \"018c0ae5-4d9b-471b-bfd6-eef314bc7037\""
+        + "},"
+        + "\"signature\": \"bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr20\""
+        + "}").replace(" ", "");
+    private static final String PROTECTING_CONTENT_ONLY_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"header\": {"
+        + "\"alg\": \"HS256\","
+        + "\"kid\": \"018c0ae5-4d9b-471b-bfd6-eef314bc7037\""
+        + "},"
+        + "\"signature\": \"xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZu"
+        + "k\""
+        + "}"
+        + "]"
+        + "}").replace(" ", "");
+    private static final String PROTECTING_CONTENT_ONLY_JSON_FLATTENED_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"header\": {"
+        + "\"alg\": \"HS256\","
+        + "\"kid\": \"018c0ae5-4d9b-471b-bfd6-eef314bc7037\""
+        + "},"
+        + "\"signature\": \"xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZuk\""
+        + "}").replace(" ", "");
+    private static final String FIRST_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES = ("{"
+        + "\"protected\": \"eyJhbGciOiJSUzI1NiJ9\","
+        + "\"header\": {"
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "},"
+        + "\"signature\": \"MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5NvyG53u"
+        + "oimic1tcMdSg-qptrzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFcryNFiHkS"
+        + "w129EghGpwkpxaTn_THJTCglNbADko1MZBCdwzJxwqZc-1RlpO2HibUY"
+        + "yXSwO97BSe0_evZKdjvvKSgsIqjytKSeAMbhMBdMma622_BG5t4sdbuC"
+        + "HtFjp9iJmkio47AIwqkZV1aIZsv33uPUqBBCXbYoQJwt7mxPftHmNlGo"
+        + "OSMxR_3thmXTCm4US-xiNOyhbm8afKK64jU6_TPtQHiJeQJxz9G3Tx-0"
+        + "83B745_AfYOnlC9w\""
+        + "}").replace(" ", "");
+    private static final String SECOND_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES = ("{"
+        + "\"header\": {"
+        + "\"alg\": \"ES512\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "},"
+        + "\"signature\": \"ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhcdCoF"
+        + "ZFFjfISu0Cdkn9Ybdlmi54ho0x924DUz8sK7ZXkhc7AFM8ObLfTvNCrq"
+        + "cI3Jkl2U5IX3utNhODH6v7xgy1Qahsn0fyb4zSAkje8bAWz4vIfj5pCM"
+        + "Yxxm4fgV3q7ZYhm5eD\""
+        + "}").replace(" ", "");
+    private static final String SECOND_SIGNATURE_UNPROTECTED_HEADER_MULTIPLE_SIGNATURES = ("{"
+        + "\"alg\": \"ES512\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "}").replace(" ", "");
+    private static final String THIRD_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES = ("{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW"
+        + "ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0\""
+        + "}").replace(" ", "");
+    private static final String MULTIPLE_SIGNATURES_JSON_GENERAL_SERIALIZATION = ("{"
+        + "\"payload\": \"SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg"
+        + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h"
+        + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi"
+        + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m"
+        + "ZiB0by4\","
+        + "\"signatures\": ["
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJSUzI1NiJ9\","
+        + "\"header\": {"
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "},"
+        + "\"signature\": \"MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5Nvy"
+        + "G53uoimic1tcMdSg-qptrzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFc"
+        + "ryNFiHkSw129EghGpwkpxaTn_THJTCglNbADko1MZBCdwzJxwqZc"
+        + "-1RlpO2HibUYyXSwO97BSe0_evZKdjvvKSgsIqjytKSeAMbhMBdM"
+        + "ma622_BG5t4sdbuCHtFjp9iJmkio47AIwqkZV1aIZsv33uPUqBBC"
+        + "XbYoQJwt7mxPftHmNlGoOSMxR_3thmXTCm4US-xiNOyhbm8afKK6"
+        + "4jU6_TPtQHiJeQJxz9G3Tx-083B745_AfYOnlC9w\""
+        + "},"
+        + "{"
+        + "\"header\": {"
+        + "\"alg\": \"ES512\","
+        + "\"kid\": \"bilbo.baggins@hobbiton.example\""
+        + "},"
+        + "\"signature\": \"ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhc"
+        + "dCoFZFFjfISu0Cdkn9Ybdlmi54ho0x924DUz8sK7ZXkhc7AFM8Ob"
+        + "LfTvNCrqcI3Jkl2U5IX3utNhODH6v7xgy1Qahsn0fyb4zSAkje8b"
+        + "AWz4vIfj5pCMYxxm4fgV3q7ZYhm5eD\""
+        + "},"
+        + "{"
+        + "\"protected\": \"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT"
+        + "RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9\","
+        + "\"signature\": \"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p"
+        + "0\""
+        + "}"
+        + "]"
+        + "}").replace(" ", "");;
+    @Test
+    public void testEncodedPayload() throws Exception {
+        assertEquals(Base64UrlUtility.encode(PAYLOAD), ENCODED_PAYLOAD);
+    }
+    @Test
+    public void testRSAv15Signature() throws Exception {
+        JwsCompactProducer compactProducer = new JwsCompactProducer(PAYLOAD);
+        compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.RS256);
+        compactProducer.getJwsHeaders().setKeyId(RSA_KID_VALUE);
+        JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+        assertEquals(reader.toJson(compactProducer.getJwsHeaders().asMap()), RSA_V1_5_SIGNATURE_PROTECTED_HEADER_JSON);
+        assertEquals(compactProducer.getUnsignedEncodedJws(),
+                RSA_V1_5_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD);
+        JsonWebKeys jwks = readKeySet("cookbookPrivateSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey rsaKey = keys.get(1);
+        compactProducer.signWith(rsaKey);
+        assertEquals(compactProducer.getSignedEncodedJws(),
+                RSA_V1_5_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD + "." + RSA_V1_5_SIGNATURE_VALUE);
+        JwsCompactConsumer compactConsumer = new JwsCompactConsumer(compactProducer.getSignedEncodedJws());
+        JsonWebKeys publicJwks = readKeySet("cookbookPublicSet.txt");
+        List<JsonWebKey> publicKeys = publicJwks.getKeys();
+        JsonWebKey rsaPublicKey = publicKeys.get(1);
+        assertTrue(compactConsumer.verifySignatureWith(rsaPublicKey, 
+                                                       SignatureAlgorithm.RS256));
+
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders protectedHeader = new JwsHeaders();
+        protectedHeader.setSignatureAlgorithm(SignatureAlgorithm.RS256);
+        protectedHeader.setKeyId(RSA_KID_VALUE);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(rsaKey, 
+                                                            SignatureAlgorithm.RS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(), RSA_V1_5_JSON_GENERAL_SERIALIZATION);
+        JwsJsonConsumer jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.RS256));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(rsaKey, SignatureAlgorithm.RS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(), RSA_V1_5_JSON_FLATTENED_SERIALIZATION);
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.RS256));
+    }
+    @Test
+    public void testRSAPSSSignature() throws Exception {
+        try {
+            Cipher.getInstance(AlgorithmUtils.PS_SHA_384_JAVA);
+        } catch (Throwable t) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+
+        JwsCompactProducer compactProducer = new JwsCompactProducer(PAYLOAD);
+        compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.PS384);
+        compactProducer.getJwsHeaders().setKeyId(RSA_KID_VALUE);
+        JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+        assertEquals(reader.toJson(compactProducer.getJwsHeaders().asMap()), RSA_PSS_SIGNATURE_PROTECTED_HEADER_JSON);
+        assertEquals(compactProducer.getUnsignedEncodedJws(),
+                RSA_PSS_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD);
+        JsonWebKeys jwks = readKeySet("cookbookPrivateSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey rsaKey = keys.get(1);
+        compactProducer.signWith(rsaKey);
+        assertEquals(compactProducer.getSignedEncodedJws().length(),
+                (RSA_PSS_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD + "." + RSA_PSS_SIGNATURE_VALUE).length());
+        JwsCompactConsumer compactConsumer = new JwsCompactConsumer(compactProducer.getSignedEncodedJws());
+        JsonWebKeys publicJwks = readKeySet("cookbookPublicSet.txt");
+        List<JsonWebKey> publicKeys = publicJwks.getKeys();
+        JsonWebKey rsaPublicKey = publicKeys.get(1);
+        assertTrue(compactConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.PS384));
+
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders protectedHeader = new JwsHeaders();
+        protectedHeader.setSignatureAlgorithm(SignatureAlgorithm.PS384);
+        protectedHeader.setKeyId(RSA_KID_VALUE);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(rsaKey, SignatureAlgorithm.PS384), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument().length(), RSA_PSS_JSON_GENERAL_SERIALIZATION.length());
+        JwsJsonConsumer jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.PS384));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(rsaKey, SignatureAlgorithm.PS384), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument().length(), RSA_PSS_JSON_FLATTENED_SERIALIZATION.length());
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.PS384));
+
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+    }
+    @Test
+    public void testECDSASignature() throws Exception {
+        
+        try {
+            Cipher.getInstance(AlgorithmUtils.ES_SHA_512_JAVA);
+        } catch (Throwable t) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+        try {
+            JwsCompactProducer compactProducer = new JwsCompactProducer(PAYLOAD);
+            compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.ES512);
+            compactProducer.getJwsHeaders().setKeyId(ECDSA_KID_VALUE);
+            JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+            assertEquals(reader.toJson(compactProducer.getJwsHeaders().asMap()), 
+                         ECDSA_SIGNATURE_PROTECTED_HEADER_JSON);
+            assertEquals(compactProducer.getUnsignedEncodedJws(),
+                    ECSDA_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD);
+            JsonWebKeys jwks = readKeySet("cookbookPrivateSet.txt");
+            List<JsonWebKey> keys = jwks.getKeys();
+            JsonWebKey ecKey = keys.get(0);
+            compactProducer.signWith(new EcDsaJwsSignatureProvider(JwkUtils.toECPrivateKey(ecKey),
+                                                                   SignatureAlgorithm.ES512));
+            assertEquals(compactProducer.getUnsignedEncodedJws(), 
+                         ECSDA_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD);
+            assertEquals(132, Base64UrlUtility.decode(compactProducer.getEncodedSignature()).length);
+            
+            JwsCompactConsumer compactConsumer = new JwsCompactConsumer(compactProducer.getSignedEncodedJws());
+            JsonWebKeys publicJwks = readKeySet("cookbookPublicSet.txt");
+            List<JsonWebKey> publicKeys = publicJwks.getKeys();
+            JsonWebKey ecPublicKey = publicKeys.get(0);
+            assertTrue(compactConsumer.verifySignatureWith(ecPublicKey, SignatureAlgorithm.ES512));
+        } finally {
+            Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        }
+    }
+    @Test
+    public void testHMACSignature() throws Exception {
+        JwsCompactProducer compactProducer = new JwsCompactProducer(PAYLOAD);
+        compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        compactProducer.getJwsHeaders().setKeyId(HMAC_KID_VALUE);
+        JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+        assertEquals(reader.toJson(compactProducer.getJwsHeaders().asMap()), HMAC_SIGNATURE_PROTECTED_HEADER_JSON);
+        assertEquals(compactProducer.getUnsignedEncodedJws(),
+                HMAC_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD);
+        JsonWebKeys jwks = readKeySet("cookbookSecretSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey key = keys.get(0);
+        compactProducer.signWith(key);
+        assertEquals(compactProducer.getSignedEncodedJws(),
+                HMAC_SIGNATURE_PROTECTED_HEADER + "." + ENCODED_PAYLOAD + "." + HMAC_SIGNATURE_VALUE);
+        JwsCompactConsumer compactConsumer = new JwsCompactConsumer(compactProducer.getSignedEncodedJws());
+        assertTrue(compactConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders protectedHeader = new JwsHeaders();
+        protectedHeader.setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        protectedHeader.setKeyId(HMAC_KID_VALUE);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(), HMAC_JSON_GENERAL_SERIALIZATION);
+        JwsJsonConsumer jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(), HMAC_JSON_FLATTENED_SERIALIZATION);
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+    }
+    @Test
+    public void testDetachedHMACSignature() throws Exception {
+        JwsCompactProducer compactProducer = new JwsCompactProducer(PAYLOAD, true);
+        compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        compactProducer.getJwsHeaders().setKeyId(HMAC_KID_VALUE);
+        JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+        assertEquals(reader.toJson(compactProducer.getJwsHeaders().asMap()), HMAC_SIGNATURE_PROTECTED_HEADER_JSON);
+        assertEquals(compactProducer.getUnsignedEncodedJws(),
+                HMAC_SIGNATURE_PROTECTED_HEADER + ".");
+        JsonWebKeys jwks = readKeySet("cookbookSecretSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey key = keys.get(0);
+        compactProducer.signWith(key);
+        assertEquals(compactProducer.getSignedEncodedJws(), DETACHED_HMAC_JWS);
+        JwsCompactConsumer compactConsumer =
+                new JwsCompactConsumer(compactProducer.getSignedEncodedJws(), ENCODED_PAYLOAD);
+        assertTrue(compactConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders protectedHeader = new JwsHeaders();
+        protectedHeader.setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        protectedHeader.setKeyId(HMAC_KID_VALUE);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(true), HMAC_DETACHED_JSON_GENERAL_SERIALIZATION);
+        JwsJsonConsumer jsonConsumer =
+                new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument(true), ENCODED_PAYLOAD);
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256), protectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(true), HMAC_DETACHED_JSON_FLATTENED_SERIALIZATION);
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument(true), ENCODED_PAYLOAD);
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+    }
+    @Test
+    public void testProtectingSpecificHeaderFieldsSignature() throws Exception {
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders protectedHeader = new JwsHeaders();
+        protectedHeader.setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        JwsHeaders unprotectedHeader = new JwsHeaders();
+        unprotectedHeader.setKeyId(HMAC_KID_VALUE);
+        JsonWebKeys jwks = readKeySet("cookbookSecretSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey key = keys.get(0);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256),
+                protectedHeader, unprotectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(),
+                PROTECTING_SPECIFIC_HEADER_FIELDS_JSON_GENERAL_SERIALIZATION);
+        JwsJsonConsumer jsonConsumer =
+                new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256),
+                protectedHeader, unprotectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(),
+                PROTECTING_SPECIFIC_HEADER_FIELDS_JSON_FLATTENED_SERIALIZATION);
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+    }
+    @Test
+    public void testProtectingContentOnlySignature() throws Exception {
+        JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+        assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+        assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+        JwsHeaders unprotectedHeader = new JwsHeaders();
+        unprotectedHeader.setSignatureAlgorithm(SignatureAlgorithm.HS256);
+        unprotectedHeader.setKeyId(HMAC_KID_VALUE);
+        JsonWebKeys jwks = readKeySet("cookbookSecretSet.txt");
+        List<JsonWebKey> keys = jwks.getKeys();
+        JsonWebKey key = keys.get(0);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256),
+                null, unprotectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(),
+                PROTECTING_CONTENT_ONLY_JSON_GENERAL_SERIALIZATION);
+        JwsJsonConsumer jsonConsumer =
+                new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+
+        jsonProducer = new JwsJsonProducer(PAYLOAD, true);
+        jsonProducer.signWith(JwsUtils.getSignatureProvider(key, SignatureAlgorithm.HS256),
+                null, unprotectedHeader);
+        assertEquals(jsonProducer.getJwsJsonSignedDocument(),
+                PROTECTING_CONTENT_ONLY_JSON_FLATTENED_SERIALIZATION);
+        jsonConsumer = new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+        assertTrue(jsonConsumer.verifySignatureWith(key, SignatureAlgorithm.HS256));
+    }
+    @Test
+    public void testMultipleSignatures() throws Exception {
+        try {
+            Cipher.getInstance(AlgorithmUtils.ES_SHA_512_JAVA);
+        } catch (Throwable t) {
+            Security.addProvider(new BouncyCastleProvider());
+        }
+        try {
+            JwsJsonProducer jsonProducer = new JwsJsonProducer(PAYLOAD);
+            assertEquals(jsonProducer.getPlainPayload(), PAYLOAD);
+            assertEquals(jsonProducer.getUnsignedEncodedPayload(), ENCODED_PAYLOAD);
+            JwsHeaders firstSignerProtectedHeader = new JwsHeaders();
+            firstSignerProtectedHeader.setSignatureAlgorithm(SignatureAlgorithm.RS256);
+            JwsHeaders firstSignerUnprotectedHeader = new JwsHeaders();
+            firstSignerUnprotectedHeader.setKeyId(RSA_KID_VALUE);
+            JsonWebKeys jwks = readKeySet("cookbookPrivateSet.txt");
+            List<JsonWebKey> keys = jwks.getKeys();
+            JsonWebKey rsaKey = keys.get(1);
+            jsonProducer.signWith(JwsUtils.getSignatureProvider(rsaKey, SignatureAlgorithm.RS256),
+                    firstSignerProtectedHeader, firstSignerUnprotectedHeader);
+            assertEquals(jsonProducer.getSignatureEntries().get(0).toJson(),
+                    FIRST_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES);
+
+            JwsHeaders secondSignerUnprotectedHeader = new JwsHeaders();
+            secondSignerUnprotectedHeader.setSignatureAlgorithm(SignatureAlgorithm.ES512);
+            secondSignerUnprotectedHeader.setKeyId(ECDSA_KID_VALUE);
+            JsonWebKey ecKey = keys.get(0);
+            jsonProducer.signWith(JwsUtils.getSignatureProvider(ecKey, SignatureAlgorithm.ES512),
+                    null, secondSignerUnprotectedHeader);
+            assertEquals(new JsonMapObjectReaderWriter().toJson(
+                jsonProducer.getSignatureEntries().get(1).getUnprotectedHeader()),
+                    SECOND_SIGNATURE_UNPROTECTED_HEADER_MULTIPLE_SIGNATURES);
+            assertEquals(jsonProducer.getSignatureEntries().get(1).toJson().length(),
+                    SECOND_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES.length());
+
+            JwsHeaders thirdSignerProtectedHeader = new JwsHeaders();
+            thirdSignerProtectedHeader.setSignatureAlgorithm(SignatureAlgorithm.HS256);
+            thirdSignerProtectedHeader.setKeyId(HMAC_KID_VALUE);
+            JsonWebKeys secretJwks = readKeySet("cookbookSecretSet.txt");
+            List<JsonWebKey> secretKeys = secretJwks.getKeys();
+            JsonWebKey hmacKey = secretKeys.get(0);
+            jsonProducer.signWith(JwsUtils.getSignatureProvider(hmacKey, SignatureAlgorithm.HS256),
+                    thirdSignerProtectedHeader);
+            assertEquals(jsonProducer.getSignatureEntries().get(2).toJson(),
+                    THIRD_SIGNATURE_ENTRY_MULTIPLE_SIGNATURES);
+            assertEquals(jsonProducer.getJwsJsonSignedDocument().length(),
+                    MULTIPLE_SIGNATURES_JSON_GENERAL_SERIALIZATION.length());
+            JwsJsonConsumer jsonConsumer =
+                    new JwsJsonConsumer(jsonProducer.getJwsJsonSignedDocument());
+            JsonWebKeys publicJwks = readKeySet("cookbookPublicSet.txt");
+            List<JsonWebKey> publicKeys = publicJwks.getKeys();
+            JsonWebKey rsaPublicKey = publicKeys.get(1);
+            JsonWebKey ecPublicKey = publicKeys.get(0);
+            assertTrue(jsonConsumer.verifySignatureWith(rsaPublicKey, SignatureAlgorithm.RS256));
+            assertTrue(jsonConsumer.verifySignatureWith(ecPublicKey, SignatureAlgorithm.ES512));
+            assertTrue(jsonConsumer.verifySignatureWith(hmacKey, SignatureAlgorithm.HS256));
+        } finally {
+            Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+        }
+    }
+    public JsonWebKeys readKeySet(String fileName) throws Exception {
+        InputStream is = JwsJoseCookBookTest.class.getResourceAsStream(fileName);
+        String s = IOUtils.readStringFromStream(is);
+        return JwkUtils.readJwkSet(s);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPrivateSet.txt
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPrivateSet.txt b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPrivateSet.txt
new file mode 100644
index 0000000..03e2704
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPrivateSet.txt
@@ -0,0 +1,24 @@
+{"keys":
+       [
+         {"kty": "EC",
+          "kid": "bilbo.baggins@hobbiton.example",
+          "use": "sig",
+          "crv": "P-521",
+          "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+          "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1",
+          "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt"
+         },
+         {"kty": "RSA",
+          "kid": "bilbo.baggins@hobbiton.example",
+          "use": "sig",
+          "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
+          "e": "AQAB",
+          "d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ",
+          "p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nRaO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmGpeNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0k",
+          "q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc",
+          "dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX59ehik",
+          "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8",
+          "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4"
+          }
+       ]
+     }

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPublicSet.txt
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPublicSet.txt b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPublicSet.txt
new file mode 100644
index 0000000..15b4808
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookPublicSet.txt
@@ -0,0 +1,16 @@
+{"keys":
+       [
+         {"kty": "EC",
+          "kid": "bilbo.baggins@hobbiton.example",
+          "use": "sig",
+          "crv": "P-521",
+          "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt",
+          "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1"
+          },
+          {"kty": "RSA",
+          "kid": "bilbo.baggins@hobbiton.example",
+          "use": "sig",
+          "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
+          "e": "AQAB"}
+       ]
+     }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookSecretSet.txt
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookSecretSet.txt b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookSecretSet.txt
new file mode 100644
index 0000000..2f4bc08
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/cookbook/cookbookSecretSet.txt
@@ -0,0 +1,16 @@
+{"keys":
+       [
+          {"kty": "oct",
+           "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037",
+           "use": "sig",
+           "alg": "HS256",
+           "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg"
+          },
+          {"kty": "oct",
+           "kid": "1e571774-2e08-40da-8308-e8d68773842d",
+           "use": "enc",
+           "alg": "A256GCM",
+           "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8"
+          }
+       ]
+     }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java
new file mode 100644
index 0000000..2cdda1a
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java
@@ -0,0 +1,288 @@
+/**
+ * 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.Security;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.apache.cxf.common.util.Base64UrlUtility;
+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.jwa.KeyAlgorithm;
+import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
+import org.apache.cxf.rs.security.jose.jws.JwsCompactReaderWriterTest;
+import org.apache.cxf.rt.security.crypto.CryptoUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JweCompactReaderWriterTest extends Assert {
+    // A1 example
+    static final byte[] CONTENT_ENCRYPTION_KEY_A1 = {
+        (byte)177, (byte)161, (byte)244, (byte)128, 84, (byte)143, (byte)225,
+        115, 63, (byte)180, 3, (byte)255, 107, (byte)154, (byte)212, (byte)246,
+        (byte)138, 7, 110, 91, 112, 46, 34, 105, 47, 
+        (byte)130, (byte)203, 46, 122, (byte)234, 64, (byte)252};
+    static final String RSA_MODULUS_ENCODED_A1 = "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW"
+           + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S"
+           + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a"
+           + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS"
+           + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj"
+           + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw";
+    static final String RSA_PUBLIC_EXPONENT_ENCODED_A1 = "AQAB";
+    static final String RSA_PRIVATE_EXPONENT_ENCODED_A1 = 
+        "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N"
+        + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9"
+        + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk"
+        + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl"
+        + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd"
+        + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ";
+    
+    static final byte[] INIT_VECTOR_A1 = {(byte)227, (byte)197, 117, (byte)252, 2, (byte)219, 
+        (byte)233, 68, (byte)180, (byte)225, 77, (byte)219};
+    
+    // A3 example
+    static final byte[] CONTENT_ENCRYPTION_KEY_A3 = {
+        4, (byte)211, 31, (byte)197, 84, (byte)157, (byte)252, (byte)254, 11, 100, 
+        (byte)157, (byte)250, 63, (byte)170, 106, (byte)206, 107, 124, (byte)212, 
+        45, 111, 107, 9, (byte)219, (byte)200, (byte)177, 0, (byte)240, (byte)143, 
+        (byte)156, 44, (byte)207};
+    static final byte[] INIT_VECTOR_A3 = {
+        3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101};
+    static final String KEY_ENCRYPTION_KEY_A3 = "GawgguFyGrWKav7AX4VKUg";
+    private static final String JWE_OUTPUT_A3 = 
+        "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0" 
+        + ".6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ" 
+        + ".AxY8DCtDaGlsbGljb3RoZQ" 
+        + ".KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY" 
+        + ".U0m_YmjN04DJvceFICbCVQ";
+    
+    private static final Boolean SKIP_AES_GCM_TESTS = isJava6();
+    
+    private static boolean isJava6() {
+        String version = System.getProperty("java.version");
+        return 1.6D == Double.parseDouble(version.substring(0, 3));    
+    }
+    
+    @BeforeClass
+    public static void registerBouncyCastleIfNeeded() throws Exception {
+        
+        try {
+            if (!SKIP_AES_GCM_TESTS) {
+                Cipher.getInstance(AlgorithmUtils.AES_GCM_ALGO_JAVA);
+            }
+            Cipher.getInstance(AlgorithmUtils.AES_CBC_ALGO_JAVA);
+        } catch (Throwable t) {
+            Security.addProvider(new BouncyCastleProvider());    
+        }
+        
+    }
+    @AfterClass
+    public static void unregisterBouncyCastleIfNeeded() throws Exception {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);    
+    }
+    
+    @Test
+    public void testEncryptDecryptAesWrapA128CBCHS256() throws Exception {
+        final String specPlainText = "Live long and prosper.";
+        
+        byte[] cekEncryptionKey = Base64UrlUtility.decode(KEY_ENCRYPTION_KEY_A3);
+        
+        AesWrapKeyEncryptionAlgorithm keyEncryption = 
+            new AesWrapKeyEncryptionAlgorithm(cekEncryptionKey, KeyAlgorithm.A128KW);
+        JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
+                                                           CONTENT_ENCRYPTION_KEY_A3, 
+                                                           INIT_VECTOR_A3,
+                                                           keyEncryption);
+        String jweContent = encryption.encrypt(specPlainText.getBytes("UTF-8"), null);
+        assertEquals(JWE_OUTPUT_A3, jweContent);
+        
+        AesWrapKeyDecryptionAlgorithm keyDecryption = new AesWrapKeyDecryptionAlgorithm(cekEncryptionKey);
+        JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
+        String decryptedText = decryption.decrypt(jweContent).getContentText();
+        assertEquals(specPlainText, decryptedText);
+    }
+    
+    @Test
+    public void testECDHESDirectKeyEncryption() throws Exception {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        ECPrivateKey bobPrivateKey = 
+            CryptoUtils.getECPrivateKey(JsonWebKey.EC_CURVE_P256, 
+                                        "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw");
+        
+        final ECPublicKey bobPublicKey = 
+            CryptoUtils.getECPublicKey(JsonWebKey.EC_CURVE_P256, 
+                                       "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
+                                       "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
+        JweEncryptionProvider jweOut = 
+            new EcdhDirectKeyJweEncryption(bobPublicKey, 
+                                           JsonWebKey.EC_CURVE_P256, 
+                                           "Alice", 
+                                           "Bob", 
+                                           ContentAlgorithm.A128GCM);
+    
+        String jweOutput = jweOut.encrypt("Hello".getBytes(), null);
+        JweDecryptionProvider jweIn = 
+            new EcdhDirectKeyJweDecryption(bobPrivateKey, ContentAlgorithm.A128GCM);
+        assertEquals("Hello", jweIn.decrypt(jweOutput).getContentText());
+    }
+    @Test
+    public void testEncryptDecryptRSA15WrapA128CBCHS256() throws Exception {
+        final String specPlainText = "Live long and prosper.";
+        
+        RSAPublicKey publicKey = CryptoUtils.getRSAPublicKey(RSA_MODULUS_ENCODED_A1, 
+                                                             RSA_PUBLIC_EXPONENT_ENCODED_A1);
+        
+        KeyEncryptionProvider keyEncryption = new RSAKeyEncryptionAlgorithm(publicKey, 
+                                                                             KeyAlgorithm.RSA1_5);
+        
+        JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
+                                                           CONTENT_ENCRYPTION_KEY_A3, 
+                                                           INIT_VECTOR_A3,
+                                                           keyEncryption);
+        String jweContent = encryption.encrypt(specPlainText.getBytes("UTF-8"), null);
+        
+        RSAPrivateKey privateKey = CryptoUtils.getRSAPrivateKey(RSA_MODULUS_ENCODED_A1, 
+                                                                RSA_PRIVATE_EXPONENT_ENCODED_A1);
+        KeyDecryptionProvider keyDecryption = new RSAKeyDecryptionAlgorithm(privateKey,
+                                                                             KeyAlgorithm.RSA1_5);
+        JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
+        String decryptedText = decryption.decrypt(jweContent).getContentText();
+        assertEquals(specPlainText, decryptedText);
+    }
+    @Test
+    public void testEncryptDecryptAesGcmWrapA128CBCHS256() throws Exception {
+        //
+        // This test fails with the IBM JDK and on Java 6
+        //
+        if ("IBM Corporation".equals(System.getProperty("java.vendor"))
+            || SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        final String specPlainText = "Live long and prosper.";
+        
+        byte[] cekEncryptionKey = Base64UrlUtility.decode(KEY_ENCRYPTION_KEY_A3);
+        
+        AesGcmWrapKeyEncryptionAlgorithm keyEncryption = 
+            new AesGcmWrapKeyEncryptionAlgorithm(cekEncryptionKey, KeyAlgorithm.A128GCMKW);
+        JweEncryptionProvider encryption = new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256,
+                                                           CONTENT_ENCRYPTION_KEY_A3, 
+                                                           INIT_VECTOR_A3,
+                                                           keyEncryption);
+        String jweContent = encryption.encrypt(specPlainText.getBytes("UTF-8"), null);
+        
+        AesGcmWrapKeyDecryptionAlgorithm keyDecryption = new AesGcmWrapKeyDecryptionAlgorithm(cekEncryptionKey);
+        JweDecryptionProvider decryption = new AesCbcHmacJweDecryption(keyDecryption);
+        String decryptedText = decryption.decrypt(jweContent).getContentText();
+        assertEquals(specPlainText, decryptedText);
+    }
+    
+    @Test
+    public void testEncryptDecryptSpecExample() throws Exception {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        final String specPlainText = "The true sign of intelligence is not knowledge but imagination.";
+        String jweContent = encryptContent(specPlainText, true);
+        
+        decrypt(jweContent, specPlainText, true);
+    }
+    
+    @Test
+    public void testDirectKeyEncryptDecrypt() throws Exception {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        final String specPlainText = "The true sign of intelligence is not knowledge but imagination.";
+        SecretKey key = createSecretKey(true);
+        String jweContent = encryptContentDirect(key, specPlainText);
+        
+        decryptDirect(key, jweContent, specPlainText);
+    }
+    
+    @Test
+    public void testEncryptDecryptJwsToken() throws Exception {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        String jweContent = encryptContent(JwsCompactReaderWriterTest.ENCODED_TOKEN_SIGNED_BY_MAC, false);
+        decrypt(jweContent, JwsCompactReaderWriterTest.ENCODED_TOKEN_SIGNED_BY_MAC, false);
+    }
+    
+    private String encryptContent(String content, boolean createIfException) throws Exception {
+        RSAPublicKey publicKey = CryptoUtils.getRSAPublicKey(RSA_MODULUS_ENCODED_A1, 
+                                                             RSA_PUBLIC_EXPONENT_ENCODED_A1);
+        SecretKey key = createSecretKey(createIfException);
+        String jwtKeyName = null;
+        if (key == null) {
+            // the encryptor will generate it
+            jwtKeyName = ContentAlgorithm.A128GCM.getJwaName();
+        } else {
+            jwtKeyName = AlgorithmUtils.toJwaName(key.getAlgorithm(), key.getEncoded().length * 8);
+        }
+        KeyEncryptionProvider keyEncryptionAlgo = new RSAKeyEncryptionAlgorithm(publicKey, 
+                                                                                 KeyAlgorithm.RSA_OAEP); 
+        ContentEncryptionProvider contentEncryptionAlgo = 
+            new AesGcmContentEncryptionAlgorithm(key == null ? null : key.getEncoded(), INIT_VECTOR_A1, 
+                ContentAlgorithm.getAlgorithm(jwtKeyName));
+        JweEncryptionProvider encryptor = new JweEncryption(keyEncryptionAlgo, contentEncryptionAlgo);
+        return encryptor.encrypt(content.getBytes("UTF-8"), null);
+    }
+    private String encryptContentDirect(SecretKey key, String content) throws Exception {
+        JweEncryption encryptor = new JweEncryption(new DirectKeyEncryptionAlgorithm(),
+            new AesGcmContentEncryptionAlgorithm(key, INIT_VECTOR_A1, ContentAlgorithm.A128GCM));
+        return encryptor.encrypt(content.getBytes("UTF-8"), null);
+    }
+    private void decrypt(String jweContent, String plainContent, boolean unwrap) throws Exception {
+        RSAPrivateKey privateKey = CryptoUtils.getRSAPrivateKey(RSA_MODULUS_ENCODED_A1, 
+                                                                RSA_PRIVATE_EXPONENT_ENCODED_A1);
+        ContentAlgorithm algo = Cipher.getMaxAllowedKeyLength("AES") > 128 
+            ? ContentAlgorithm.A256GCM : ContentAlgorithm.A128GCM; 
+        JweDecryptionProvider decryptor = new JweDecryption(new RSAKeyDecryptionAlgorithm(privateKey),
+                                              new AesGcmContentDecryptionAlgorithm(algo));
+        String decryptedText = decryptor.decrypt(jweContent).getContentText();
+        assertEquals(decryptedText, plainContent);
+    }
+    private void decryptDirect(SecretKey key, String jweContent, String plainContent) throws Exception {
+        JweDecryption decryptor = new JweDecryption(new DirectKeyDecryptionAlgorithm(key), 
+                                               new AesGcmContentDecryptionAlgorithm(ContentAlgorithm.A128GCM));
+        String decryptedText = decryptor.decrypt(jweContent).getContentText();
+        assertEquals(decryptedText, plainContent);
+    }
+    private SecretKey createSecretKey(boolean createIfException) throws Exception {
+        SecretKey key = null;
+        if (Cipher.getMaxAllowedKeyLength("AES") > 128) { 
+            key = CryptoUtils.createSecretKeySpec(CONTENT_ENCRYPTION_KEY_A1, "AES");
+        } else if (createIfException) {
+            key = CryptoUtils.createSecretKeySpec(CryptoUtils.generateSecureRandomBytes(128 / 8), "AES");
+        }
+        return key;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonConsumerTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonConsumerTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonConsumerTest.java
new file mode 100644
index 0000000..de7bc94
--- /dev/null
+++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweJsonConsumerTest.java
@@ -0,0 +1,184 @@
+/**
+ * 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.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.apache.cxf.common.util.Base64UrlUtility;
+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.jwa.KeyAlgorithm;
+import org.apache.cxf.rt.security.crypto.CryptoUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class JweJsonConsumerTest extends Assert {
+
+    static final String SINGLE_RECIPIENT_ALL_HEADERS_AAD_MODIFIED_OUTPUT = 
+        "{" 
+        + "\"protected\":\"eyJlbmMiOiJBMTI4R0NNIn0\","
+        + "\"unprotected\":{\"jku\":\"https://server.example.com/keys.jwks\"},"    
+        + "\"recipients\":" 
+        + "["
+        + "{"
+        + "\"header\":{\"alg\":\"A128KW\"},"
+        + "\"encrypted_key\":\"b3-M9_CRgT3wEBhhXlpb-BoY7vtA4W_N\""
+        + "}"
+        + "],"
+        + "\"aad\":\"" + Base64UrlUtility.encode(JweJsonProducerTest.EXTRA_AAD_SOURCE + ".") + "\","
+        + "\"iv\":\"48V1_ALb6US04U3b\","
+        + "\"ciphertext\":\"KTuJBMk9QG59xPB-c_YLM5-J7VG40_eMPvyHDD7eB-WHj_34YiWgpBOydTBm4RW0zUCJZ09xqorhWJME-DcQ\","
+        + "\"tag\":\"oVUQGS9608D-INq61-vOaA\""
+        + "}";
+    
+    private static final Boolean SKIP_AES_GCM_TESTS = isJava6();
+    
+    private static boolean isJava6() {
+        String version = System.getProperty("java.version");
+        return 1.6D == Double.parseDouble(version.substring(0, 3));    
+    }
+
+
+    @BeforeClass
+    public static void registerBouncyCastleIfNeeded() throws Exception {
+        try {
+            Cipher.getInstance(AlgorithmUtils.AES_GCM_ALGO_JAVA);
+            Cipher.getInstance(AlgorithmUtils.AES_CBC_ALGO_JAVA);
+        } catch (Throwable t) {
+            Security.addProvider(new BouncyCastleProvider());    
+        }
+    }
+    @AfterClass
+    public static void unregisterBouncyCastleIfNeeded() throws Exception {
+        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);    
+    }
+    
+    @Test
+    public void testSingleRecipientGcm() throws Exception {
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        doTestSingleRecipient(text, 
+                              JweJsonProducerTest.SINGLE_RECIPIENT_OUTPUT, 
+                              ContentAlgorithm.A128GCM, 
+                              JweJsonProducerTest.WRAPPER_BYTES1,
+                              null);
+    }
+    @Test
+    public void testSingleRecipientFlatGcm() throws Exception {
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        doTestSingleRecipient(text, 
+                              JweJsonProducerTest.SINGLE_RECIPIENT_FLAT_OUTPUT, 
+                              ContentAlgorithm.A128GCM, 
+                              JweJsonProducerTest.WRAPPER_BYTES1,
+                              null);
+    }
+    @Test
+    public void testSingleRecipientDirectGcm() throws Exception {
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        doTestSingleRecipient(text, 
+                              JweJsonProducerTest.SINGLE_RECIPIENT_DIRECT_OUTPUT, 
+                              ContentAlgorithm.A128GCM, 
+                              null, 
+                              JweJsonProducerTest.CEK_BYTES);
+    }
+    @Test
+    public void testSingleRecipientDirectA128CBCHS256() throws Exception {
+        String text = "Live long and prosper.";
+        doTestSingleRecipient(text, 
+                              JweJsonProducerTest.SINGLE_RECIPIENT_A128CBCHS256_DIRECT_OUTPUT, 
+                              ContentAlgorithm.A128CBC_HS256, 
+                              null,
+                              JweCompactReaderWriterTest.CONTENT_ENCRYPTION_KEY_A3);
+    }
+    @Test
+    public void testSingleRecipientA128CBCHS256() throws Exception {
+        String text = "Live long and prosper.";
+        doTestSingleRecipient(text, 
+                              JweJsonProducerTest.SINGLE_RECIPIENT_A128CBCHS256_OUTPUT, 
+                              ContentAlgorithm.A128CBC_HS256, 
+                              Base64UrlUtility.decode(JweCompactReaderWriterTest.KEY_ENCRYPTION_KEY_A3),
+                              null);
+    }
+    @Test
+    public void testSingleRecipientAllTypeOfHeadersAndAad() {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        final String text = "The true sign of intelligence is not knowledge but imagination.";
+        
+        SecretKey wrapperKey = CryptoUtils.createSecretKeySpec(JweJsonProducerTest.WRAPPER_BYTES1, 
+                                                               "AES");
+        JweDecryptionProvider jwe = JweUtils.createJweDecryptionProvider(wrapperKey, 
+                                                                         KeyAlgorithm.A128KW, 
+                                                                         ContentAlgorithm.A128GCM);
+        JweJsonConsumer consumer = new JweJsonConsumer(JweJsonProducerTest.SINGLE_RECIPIENT_ALL_HEADERS_AAD_OUTPUT);
+        JweDecryptionOutput out = consumer.decryptWith(jwe);
+        assertEquals(text, out.getContentText());
+        assertEquals(JweJsonProducerTest.EXTRA_AAD_SOURCE, consumer.getAadText());
+    }
+    @Test
+    public void testSingleRecipientAllTypeOfHeadersAndAadModified() {
+        if (SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        SecretKey wrapperKey = CryptoUtils.createSecretKeySpec(JweJsonProducerTest.WRAPPER_BYTES1, 
+                                                               "AES");
+        JweDecryptionProvider jwe = JweUtils.createJweDecryptionProvider(wrapperKey, 
+                                                                         KeyAlgorithm.A128KW, 
+                                                                         ContentAlgorithm.A128GCM);
+        JweJsonConsumer consumer = new JweJsonConsumer(SINGLE_RECIPIENT_ALL_HEADERS_AAD_MODIFIED_OUTPUT);
+        try {
+            consumer.decryptWith(jwe);
+            fail("AAD check has passed unexpectedly");
+        } catch (SecurityException ex) {
+            // expected
+        }
+        
+    }
+    private void doTestSingleRecipient(String text,
+                                       String input, 
+                                       ContentAlgorithm contentEncryptionAlgo,
+                                       final byte[] wrapperKeyBytes,
+                                       final byte[] cek) throws Exception {
+        if (AlgorithmUtils.A128GCM_ALGO.equals(contentEncryptionAlgo.getJwaName()) && SKIP_AES_GCM_TESTS) {
+            return;
+        }
+        JweDecryptionProvider jwe = null;
+        if (wrapperKeyBytes != null) {
+            SecretKey wrapperKey = CryptoUtils.createSecretKeySpec(wrapperKeyBytes, "AES");
+            jwe = JweUtils.createJweDecryptionProvider(wrapperKey, 
+                                                       KeyAlgorithm.A128KW, 
+                                                       contentEncryptionAlgo);
+        } else {
+            SecretKey cekKey = CryptoUtils.createSecretKeySpec(cek, "AES");
+            jwe = JweUtils.getDirectKeyJweDecryption(cekKey, contentEncryptionAlgo);
+        }
+        JweJsonConsumer consumer = new JweJsonConsumer(input);
+        JweDecryptionOutput out = consumer.decryptWith(jwe);
+        assertEquals(text, out.getContentText());
+    }
+    
+}
+


Mime
View raw message