You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2014/10/31 16:38:01 UTC

git commit: [CXF-6053] Prototyping JwSJsonOutputStream and supporting multiple key aliases in a single properties file

Repository: cxf
Updated Branches:
  refs/heads/master 56c0db051 -> d20d51801


[CXF-6053] Prototyping JwSJsonOutputStream and supporting multiple key aliases in a single properties file


Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/d20d5180
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/d20d5180
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/d20d5180

Branch: refs/heads/master
Commit: d20d518014f1df8bf6d3befa0af2ebc378a8d488
Parents: 56c0db0
Author: Sergey Beryozkin <sb...@talend.com>
Authored: Fri Oct 31 15:37:43 2014 +0000
Committer: Sergey Beryozkin <sb...@talend.com>
Committed: Fri Oct 31 15:37:43 2014 +0000

----------------------------------------------------------------------
 .../cxf/common/util/Base64OutputStream.java     |   4 +-
 .../jaxrs/AbstractJwsJsonReaderProvider.java    |   6 +-
 .../jaxrs/AbstractJwsJsonWriterProvider.java    |   8 +-
 .../jose/jaxrs/JwsJsonWriterInterceptor.java    |  63 +++++++++---
 .../security/jose/jaxrs/KeyManagementUtils.java |   1 +
 .../cxf/rs/security/jose/jwk/JwkUtils.java      |  75 ++++++++++----
 .../security/jose/jws/JwsJsonOutputStream.java  | 103 +++++++++++++++++++
 .../jose/jws/JwsJsonProtectedHeader.java        |   5 +-
 .../cxf/rs/security/jose/jws/JwsUtils.java      |  76 +++++++++++---
 .../systest/jaxrs/security/jwt/BookStore.java   |  10 ++
 .../jaxrs/security/jwt/JAXRSJweJwsTest.java     |  21 +++-
 .../jaxrs/security/jwt/JAXRSJwsJsonTest.java    |  27 +++--
 12 files changed, 335 insertions(+), 64 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/core/src/main/java/org/apache/cxf/common/util/Base64OutputStream.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/cxf/common/util/Base64OutputStream.java b/core/src/main/java/org/apache/cxf/common/util/Base64OutputStream.java
index 6ba8e95..61724d8 100644
--- a/core/src/main/java/org/apache/cxf/common/util/Base64OutputStream.java
+++ b/core/src/main/java/org/apache/cxf/common/util/Base64OutputStream.java
@@ -65,14 +65,14 @@ public class Base64OutputStream extends FilterOutputStream {
     
     @Override
     public void flush() throws IOException {
-        if (flushed) {
+        if (flushed || lastChunk == null) {
             return;
         }
         try {
             Base64Utility.encodeAndStream(lastChunk, 0, lastChunk.length, urlSafe, out);
             lastChunk = null;
         } catch (Exception ex) {
-            throw new SecurityException();
+            throw new IOException(ex);
         }
         flushed = true;
     }

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
index bf2bf2f..7272df9 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonReaderProvider.java
@@ -18,6 +18,7 @@
  */
 package org.apache.cxf.rs.security.jose.jaxrs;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -55,13 +56,14 @@ public class AbstractJwsJsonReaderProvider {
         }
         List<String> propLocs = null;
         if (propLocsProp instanceof String) { 
-            propLocs = Collections.singletonList((String)propLocsProp);
+            String[] props = ((String)propLocsProp).split(",");
+            propLocs = Arrays.asList(props);
         } else {
             propLocs = CastUtils.cast((List<?>)propLocsProp);
         }
         List<JwsSignatureVerifier> theSigVerifiers = new LinkedList<JwsSignatureVerifier>();
         for (String propLoc : propLocs) {
-            theSigVerifiers.add(JwsUtils.loadSignatureVerifier(propLoc, m));
+            theSigVerifiers.addAll(JwsUtils.loadSignatureVerifiers(propLoc, m));
         }
         return theSigVerifiers;
     }

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonWriterProvider.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonWriterProvider.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonWriterProvider.java
index 6aa9695..db10c62 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonWriterProvider.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/AbstractJwsJsonWriterProvider.java
@@ -21,6 +21,7 @@ package org.apache.cxf.rs.security.jose.jaxrs;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -59,14 +60,15 @@ public class AbstractJwsJsonWriterProvider {
             throw new SecurityException();
         }
         List<String> propLocs = null;
-        if (propLocsProp instanceof String) { 
-            propLocs = Collections.singletonList((String)propLocsProp);
+        if (propLocsProp instanceof String) {
+            String[] props = ((String)propLocsProp).split(",");
+            propLocs = Arrays.asList(props);
         } else {
             propLocs = CastUtils.cast((List<?>)propLocsProp);
         }
         List<JwsSignatureProvider> theSigProviders = new LinkedList<JwsSignatureProvider>();
         for (String propLoc : propLocs) {
-            theSigProviders.add(JwsUtils.loadSignatureProvider(propLoc, m));
+            theSigProviders.addAll(JwsUtils.loadSignatureProviders(propLoc, m));
         }
         return theSigProviders;
     }

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonWriterInterceptor.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonWriterInterceptor.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonWriterInterceptor.java
index e812e59..1417cf0 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonWriterInterceptor.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwsJsonWriterInterceptor.java
@@ -20,6 +20,7 @@ package org.apache.cxf.rs.security.jose.jaxrs;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.annotation.Priority;
@@ -28,42 +29,76 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.ext.WriterInterceptor;
 import javax.ws.rs.ext.WriterInterceptorContext;
 
+import org.apache.cxf.common.util.Base64UrlOutputStream;
+import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.io.CachedOutputStream;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.rs.security.jose.JoseConstants;
 import org.apache.cxf.rs.security.jose.JoseHeaders;
+import org.apache.cxf.rs.security.jose.jws.JwsJsonOutputStream;
 import org.apache.cxf.rs.security.jose.jws.JwsJsonProducer;
 import org.apache.cxf.rs.security.jose.jws.JwsJsonProtectedHeader;
+import org.apache.cxf.rs.security.jose.jws.JwsSignature;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
 
 @Priority(Priorities.JWS_WRITE_PRIORITY)
 public class JwsJsonWriterInterceptor extends AbstractJwsJsonWriterProvider implements WriterInterceptor {
     private boolean contentTypeRequired = true;
+    private boolean useJwsOutputStream;
     @Override
     public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException, WebApplicationException {
         
         List<JwsSignatureProvider> sigProviders = getInitializedSigProviders();
         OutputStream actualOs = ctx.getOutputStream();
-        CachedOutputStream cos = new CachedOutputStream(); 
-        ctx.setOutputStream(cos);
-        ctx.proceed();
-        JwsJsonProducer p = new JwsJsonProducer(new String(cos.getBytes(), "UTF-8"));
-        for (JwsSignatureProvider signer : sigProviders) {
-            JoseHeaders headers = new JoseHeaders();
-            headers.setAlgorithm(signer.getAlgorithm());
-            setContentTypeIfNeeded(headers, ctx);
-            //TODO: support setting public JWK kid property as the unprotected header;
-            //      the property would have to be associated with the individual signer
-            p.signWith(signer, new JwsJsonProtectedHeader(headers), null);    
+        if (useJwsOutputStream) {
+            List<String> protectedHeaders = new ArrayList<String>(sigProviders.size());
+            List<JwsSignature> signatures = new ArrayList<JwsSignature>(sigProviders.size());
+            for (JwsSignatureProvider signer : sigProviders) {
+                JwsJsonProtectedHeader protectedHeader = prepareProtectedHeader(ctx, signer);
+                String encoded = protectedHeader.getEncodedHeaderEntries();
+                protectedHeaders.add(encoded);
+                JwsSignature signature = signer.createJwsSignature(protectedHeader.getHeaderEntries());
+                byte[] start = StringUtils.toBytesUTF8(encoded + ".");
+                signature.update(start, 0, start.length);
+                signatures.add(signature);
+            }    
+            ctx.setMediaType(JAXRSUtils.toMediaType(JoseConstants.MEDIA_TYPE_JOSE_JSON));
+            actualOs.write(StringUtils.toBytesUTF8("{\"payload\":\""));
+            JwsJsonOutputStream jwsStream = new JwsJsonOutputStream(actualOs, protectedHeaders, signatures);
+            Base64UrlOutputStream base64Stream = new Base64UrlOutputStream(jwsStream);
+            ctx.setOutputStream(base64Stream);
+            ctx.proceed();
+            base64Stream.flush();
+            jwsStream.flush();
+        } else {
+            CachedOutputStream cos = new CachedOutputStream(); 
+            ctx.setOutputStream(cos);
+            ctx.proceed();
+            JwsJsonProducer p = new JwsJsonProducer(new String(cos.getBytes(), "UTF-8"));
+            for (JwsSignatureProvider signer : sigProviders) {
+                JwsJsonProtectedHeader protectedHeader = prepareProtectedHeader(ctx, signer);
+                p.signWith(signer, protectedHeader, null);    
+            }
+            ctx.setMediaType(JAXRSUtils.toMediaType(JoseConstants.MEDIA_TYPE_JOSE_JSON));
+            writeJws(p, actualOs);
         }
-        ctx.setMediaType(JAXRSUtils.toMediaType(JoseConstants.MEDIA_TYPE_JOSE_JSON));
-        writeJws(p, actualOs);
+        
+    }
+    
+    private JwsJsonProtectedHeader prepareProtectedHeader(WriterInterceptorContext ctx, 
+                                                          JwsSignatureProvider signer) {
+        JoseHeaders headers = new JoseHeaders();
+        headers.setAlgorithm(signer.getAlgorithm());
+        setContentTypeIfNeeded(headers, ctx);
+        return new JwsJsonProtectedHeader(headers);
     }
     
     public void setContentTypeRequired(boolean contentTypeRequired) {
         this.contentTypeRequired = contentTypeRequired;
     }
-    
+    public void setUseJwsJsonOutputStream(boolean useJwsJsonOutputStream) {
+        this.useJwsOutputStream = useJwsJsonOutputStream;
+    }
     private void setContentTypeIfNeeded(JoseHeaders headers, WriterInterceptorContext ctx) {    
         if (contentTypeRequired) {
             MediaType mt = ctx.getMediaType();

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/KeyManagementUtils.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/KeyManagementUtils.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/KeyManagementUtils.java
index 369e072..88fcb3a 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/KeyManagementUtils.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/KeyManagementUtils.java
@@ -42,6 +42,7 @@ public final class KeyManagementUtils {
     public static final String RSSEC_KEY_STORE_PSWD = "rs.security.keystore.password";
     public static final String RSSEC_KEY_PSWD = "rs.security.key.password";
     public static final String RSSEC_KEY_STORE_ALIAS = "rs.security.keystore.alias";
+    public static final String RSSEC_KEY_STORE_ALIASES = "rs.security.keystore.aliases";
     public static final String RSSEC_KEY_STORE_FILE = "rs.security.keystore.file";
     public static final String RSSEC_PRINCIPAL_NAME = "rs.security.principal.name";
     public static final String RSSEC_KEY_PSWD_PROVIDER = "rs.security.key.password.provider";

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
index ac6654f..883a2b4 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java
@@ -25,6 +25,7 @@ import java.security.interfaces.ECPrivateKey;
 import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
@@ -199,40 +200,78 @@ public final class JwkUtils {
     public static JsonWebKey loadJsonWebKey(Message m, Properties props, String keyOper) {
         return loadJsonWebKey(m, props, keyOper, new DefaultJwkReaderWriter());
     }
+
     public static JsonWebKey loadJsonWebKey(Message m, Properties props, String keyOper, JwkReaderWriter reader) {
-        PrivateKeyPasswordProvider cb = 
-            (PrivateKeyPasswordProvider)m.getContextualProperty(KeyManagementUtils.RSSEC_KEY_PSWD_PROVIDER);
-        if (cb == null && keyOper != null) {
-            String propName = keyOper.equals(JsonWebKey.KEY_OPER_SIGN) ? KeyManagementUtils.RSSEC_SIG_KEY_PSWD_PROVIDER
-                : keyOper.equals(JsonWebKey.KEY_OPER_ENCRYPT) 
-                ? KeyManagementUtils.RSSEC_DECRYPT_KEY_PSWD_PROVIDER : null;
-            if (propName != null) {
-                cb = (PrivateKeyPasswordProvider)m.getContextualProperty(propName);
+        PrivateKeyPasswordProvider cb = loadPasswordProvider(m, props, keyOper);
+        JsonWebKeys jwkSet = loadJwkSet(m, props, cb, reader);
+        String kid = getKeyId(props, KeyManagementUtils.RSSEC_KEY_STORE_ALIAS, keyOper);
+        if (kid != null) {
+            return jwkSet.getKey(kid);
+        } else if (keyOper != null) {
+            List<JsonWebKey> keys = jwkSet.getKeyUseMap().get(keyOper);
+            if (keys != null && keys.size() == 1) {
+                return keys.get(0);
             }
         }
+        return null;
+    }
+    public static List<JsonWebKey> loadJsonWebKeys(Message m, Properties props, String keyOper) {
+        return loadJsonWebKeys(m, props, keyOper, new DefaultJwkReaderWriter());
+    }
+
+    public static List<JsonWebKey> loadJsonWebKeys(Message m, Properties props, String keyOper, 
+                                                   JwkReaderWriter reader) {
+        PrivateKeyPasswordProvider cb = loadPasswordProvider(m, props, keyOper);
         JsonWebKeys jwkSet = loadJwkSet(m, props, cb, reader);
-        String kid = props.getProperty(KeyManagementUtils.RSSEC_KEY_STORE_ALIAS);
+        String kid = getKeyId(props, KeyManagementUtils.RSSEC_KEY_STORE_ALIAS, keyOper);
+        if (kid != null) {
+            return Collections.singletonList(jwkSet.getKey(kid));
+        }
+        String kids = getKeyId(props, KeyManagementUtils.RSSEC_KEY_STORE_ALIASES, keyOper);
+        if (kids != null) {
+            String[] values = kids.split(",");
+            List<JsonWebKey> keys = new ArrayList<JsonWebKey>(values.length);
+            for (String value : values) {
+                keys.add(jwkSet.getKey(value));
+            }
+            return keys;
+        }
+        if (keyOper != null) {
+            List<JsonWebKey> keys = jwkSet.getKeyUseMap().get(keyOper);
+            if (keys != null && keys.size() == 1) {
+                return Collections.singletonList(keys.get(0));
+            }
+        }
+        return null;
+    }
+    private static String getKeyId(Properties props, String propertyName, String keyOper) {
+        String kid = props.getProperty(propertyName);
         if (kid == null && keyOper != null) {
             String keyIdProp = null;
             if (keyOper.equals(JsonWebKey.KEY_OPER_ENCRYPT)) {
-                keyIdProp = KeyManagementUtils.RSSEC_KEY_STORE_ALIAS + ".jwe";
+                keyIdProp = propertyName + ".jwe";
             } else if (keyOper.equals(JsonWebKey.KEY_OPER_SIGN)
                        || keyOper.equals(JsonWebKey.KEY_OPER_VERIFY)) {
-                keyIdProp = KeyManagementUtils.RSSEC_KEY_STORE_ALIAS + ".jws";
+                keyIdProp = propertyName + ".jws";
             }
             if (keyIdProp != null) {
                 kid = props.getProperty(keyIdProp);
             }
         }
-        if (kid != null) {
-            return jwkSet.getKey(kid);
-        } else if (keyOper != null) {
-            List<JsonWebKey> keys = jwkSet.getKeyUseMap().get(keyOper);
-            if (keys != null && keys.size() == 1) {
-                return keys.get(0);
+        return kid;
+    }
+    public static PrivateKeyPasswordProvider loadPasswordProvider(Message m, Properties props, String keyOper) {
+        PrivateKeyPasswordProvider cb = 
+            (PrivateKeyPasswordProvider)m.getContextualProperty(KeyManagementUtils.RSSEC_KEY_PSWD_PROVIDER);
+        if (cb == null && keyOper != null) {
+            String propName = keyOper.equals(JsonWebKey.KEY_OPER_SIGN) ? KeyManagementUtils.RSSEC_SIG_KEY_PSWD_PROVIDER
+                : keyOper.equals(JsonWebKey.KEY_OPER_ENCRYPT) 
+                ? KeyManagementUtils.RSSEC_DECRYPT_KEY_PSWD_PROVIDER : null;
+            if (propName != null) {
+                cb = (PrivateKeyPasswordProvider)m.getContextualProperty(propName);
             }
         }
-        return null;
+        return cb;
     }
     public static RSAPublicKey toRSAPublicKey(JsonWebKey jwk) {
         String encodedModulus = (String)jwk.getProperty(JsonWebKey.RSA_MODULUS);

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonOutputStream.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonOutputStream.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonOutputStream.java
new file mode 100644
index 0000000..7fc1007
--- /dev/null
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonOutputStream.java
@@ -0,0 +1,103 @@
+/**
+ * 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.jws;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cxf.common.util.Base64UrlUtility;
+import org.apache.cxf.common.util.StringUtils;
+
+public class JwsJsonOutputStream extends FilterOutputStream {
+    private boolean flushed;
+    private List<String> protectedHeaders;
+    private List<JwsSignature> signatures;
+    private ExecutorService executor;
+    public JwsJsonOutputStream(OutputStream out, 
+                               List<String> protectedHeaders,
+                               List<JwsSignature> signatures) {
+        super(out);
+        this.protectedHeaders = protectedHeaders;
+        this.signatures = signatures;
+        // This can be further optimized by having a dedicated thread per signature,
+        // can make a difference if a number of signatures to be created is > 2
+        this.executor = Executors.newSingleThreadExecutor();
+    }
+
+    @Override
+    public void write(int value) throws IOException {
+        byte[] bytes = ByteBuffer.allocate(Integer.SIZE / 8).putInt(value).array();
+        write(bytes, 0, bytes.length);
+    }
+    
+    @Override
+    public void write(final byte b[], final int off, final int len) throws IOException {
+        //TODO: Review if it is at least theoretically possible that a given b[] region
+        // can be modified in a subsequent write which might affect the signature calculation  
+        executor.execute(new Runnable() {
+            public void run() {
+                for (JwsSignature signature : signatures) {
+                    try {
+                        signature.update(b, off, len);
+                    } catch (Throwable ex) {
+                        throw new SecurityException();
+                    }
+                }
+            }
+        });
+        out.write(b, off, len);
+    }
+    @Override
+    public void flush() throws IOException {
+        if (flushed) {
+            return;
+        }
+        out.write(StringUtils.toBytesUTF8("\",\"signatures\":["));
+        try {
+            shutdownExecutor();
+            for (int i = 0; i < signatures.size(); i++) {
+                if (i > 0) {
+                    out.write(new byte[]{','});
+                }
+                out.write(StringUtils.toBytesUTF8("{\"protected\":\"" 
+                                                 + protectedHeaders.get(i) 
+                                                 + "\",\"signature\":\""));
+                byte[] sign = signatures.get(i).sign();
+                Base64UrlUtility.encodeAndStream(sign, 0, sign.length, out);
+                out.write(StringUtils.toBytesUTF8("\"}"));
+            }
+        } catch (Exception ex) {
+            throw new SecurityException();
+        }
+        out.write(StringUtils.toBytesUTF8("]}"));
+        flushed = true;
+    }
+    private void shutdownExecutor() throws Exception {
+        executor.shutdown();
+        while (!executor.isTerminated()) {
+            executor.awaitTermination(1, TimeUnit.MILLISECONDS);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProtectedHeader.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProtectedHeader.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProtectedHeader.java
index 05cecdb..dd94b25 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProtectedHeader.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProtectedHeader.java
@@ -49,8 +49,11 @@ public class JwsJsonProtectedHeader {
     public Object getHeader(String name) {
         return headerEntries.getHeader(name);
     }
+    public String toJson() {
+        return writer.headersToJson(headerEntries);
+    }
     public String getEncodedHeaderEntries() {
-        return Base64UrlUtility.encode(writer.headersToJson(headerEntries));
+        return Base64UrlUtility.encode(toJson());
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java
index c9741a2..21cb1e1 100644
--- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java
+++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsUtils.java
@@ -20,6 +20,8 @@ package org.apache.cxf.rs.security.jose.jws;
 
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Properties;
 
@@ -84,12 +86,10 @@ public final class JwsUtils {
         return map;
     }
     public static JwsSignatureProvider loadSignatureProvider(String propLoc, Message m) {
-        Properties props = null;
-        try {
-            props = ResourceUtils.loadProperties(propLoc, m.getExchange().getBus());
-        } catch (Exception ex) {
-            throw new SecurityException(ex);
-        }
+        return loadSignatureProvider(propLoc, m, false);
+    }
+    private static JwsSignatureProvider loadSignatureProvider(String propLoc, Message m, boolean ignoreNullProvider) {
+        Properties props = loadProperties(m, propLoc);
         JwsSignatureProvider theSigProvider = null; 
         String rsaSignatureAlgo = null;
         if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) {
@@ -102,18 +102,37 @@ public final class JwsUtils {
                 KeyManagementUtils.RSSEC_SIG_KEY_PSWD_PROVIDER);
             theSigProvider = new PrivateKeyJwsSignatureProvider(pk, rsaSignatureAlgo);
         }
-        if (theSigProvider == null) {
+        if (theSigProvider == null && !ignoreNullProvider) {
             throw new SecurityException();
         }
         return theSigProvider;
     }
-    public static JwsSignatureVerifier loadSignatureVerifier(String propLoc, Message m) {
-        Properties props = null;
-        try {
-            props = ResourceUtils.loadProperties(propLoc, m.getExchange().getBus());
-        } catch (Exception ex) {
-            throw new SecurityException(ex);
+    public static List<JwsSignatureProvider> loadSignatureProviders(String propLoc, Message m) {
+        Properties props = loadProperties(m, propLoc);
+        JwsSignatureProvider theSigProvider = loadSignatureProvider(propLoc, m, true);
+        if (theSigProvider != null) {
+            return Collections.singletonList(theSigProvider);
+        }
+        List<JwsSignatureProvider> theSigProviders = null; 
+        if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) {
+            List<JsonWebKey> jwks = JwkUtils.loadJsonWebKeys(m, props, JsonWebKey.KEY_OPER_SIGN);
+            if (jwks != null) {
+                theSigProviders = new ArrayList<JwsSignatureProvider>(jwks.size());
+                for (JsonWebKey jwk : jwks) {
+                    theSigProviders.add(JwsUtils.getSignatureProvider(jwk));
+                }
+            }
         }
+        if (theSigProviders == null) {
+            throw new SecurityException();
+        }
+        return theSigProviders;
+    }
+    public static JwsSignatureVerifier loadSignatureVerifier(String propLoc, Message m) {
+        return loadSignatureVerifier(propLoc, m, false);
+    }
+    public static JwsSignatureVerifier loadSignatureVerifier(String propLoc, Message m, boolean ignoreNullVerifier) {
+        Properties props = loadProperties(m, propLoc);
         JwsSignatureVerifier theVerifier = null;
         String rsaSignatureAlgo = null;
         if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) {
@@ -126,8 +145,39 @@ public final class JwsUtils {
             theVerifier = new PublicKeyJwsSignatureVerifier(
                               (RSAPublicKey)KeyManagementUtils.loadPublicKey(m, props), rsaSignatureAlgo);
         }
+        if (theVerifier == null && !ignoreNullVerifier) {
+            throw new SecurityException();
+        }
         return theVerifier;
     }
+    public static List<JwsSignatureVerifier> loadSignatureVerifiers(String propLoc, Message m) {
+        Properties props = loadProperties(m, propLoc);
+        JwsSignatureVerifier theVerifier = loadSignatureVerifier(propLoc, m, true);
+        if (theVerifier != null) {
+            return Collections.singletonList(theVerifier);
+        }
+        List<JwsSignatureVerifier> theVerifiers = null; 
+        if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) {
+            List<JsonWebKey> jwks = JwkUtils.loadJsonWebKeys(m, props, JsonWebKey.KEY_OPER_SIGN);
+            if (jwks != null) {
+                theVerifiers = new ArrayList<JwsSignatureVerifier>(jwks.size());
+                for (JsonWebKey jwk : jwks) {
+                    theVerifiers.add(JwsUtils.getSignatureVerifier(jwk));
+                }
+            }
+        }
+        if (theVerifiers == null) {
+            throw new SecurityException();
+        }
+        return theVerifiers;
+    }
+    private static Properties loadProperties(Message m, String propLoc) {
+        try {
+            return ResourceUtils.loadProperties(propLoc, m.getExchange().getBus());
+        } catch (Exception ex) {
+            throw new SecurityException(ex);
+        }
+    }
     private static String getSignatureAlgo(Properties props, String algo) {
         return algo == null ? props.getProperty(JSON_WEB_SIGNATURE_ALGO_PROP) : algo;
     }

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/BookStore.java
----------------------------------------------------------------------
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/BookStore.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/BookStore.java
index 0bc010e..b56ce36 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/BookStore.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/BookStore.java
@@ -25,6 +25,8 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 
+import org.apache.cxf.systest.jaxrs.security.Book;
+
 @Path("/bookstore")
 public class BookStore {
     
@@ -39,6 +41,14 @@ public class BookStore {
         return text;
     }
     
+    @POST
+    @Path("/books")
+    @Produces("application/json")
+    @Consumes("application/json")
+    public Book echoBook(Book book) {
+        return book;
+    }
+    
 }
 
 

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJweJwsTest.java
----------------------------------------------------------------------
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJweJwsTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJweJwsTest.java
index 740c6a8..119aa36 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJweJwsTest.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJweJwsTest.java
@@ -42,6 +42,7 @@ import org.apache.cxf.rs.security.jose.jwe.AesWrapKeyDecryptionAlgorithm;
 import org.apache.cxf.rs.security.jose.jwe.AesWrapKeyEncryptionAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
 import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider;
+import org.apache.cxf.systest.jaxrs.security.Book;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
@@ -78,8 +79,21 @@ public class JAXRSJweJwsTest extends AbstractBusClientServerTestBase {
         Security.removeProvider(BouncyCastleProvider.class.getName());    
     }
     @Test
-    public void testJweJwkRSA() throws Exception {
+    public void testJweJwkPlainTextRSA() throws Exception {
         String address = "https://localhost:" + PORT + "/jwejwkrsa";
+        BookStore bs = createBookStore(address);
+        String text = bs.echoText("book");
+        assertEquals("book", text);
+    }
+    @Test
+    public void testJweJwkBookBeanRSA() throws Exception {
+        String address = "https://localhost:" + PORT + "/jwejwkrsa";
+        BookStore bs = createBookStore(address);
+        Book book = bs.echoBook(new Book("book", 123L));
+        assertEquals("book", book.getName());
+        assertEquals(123L, book.getId());
+    }
+    private BookStore createBookStore(String address) throws Exception {
         JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
         SpringBusFactory bf = new SpringBusFactory();
         URL busFile = JAXRSJweJwsTest.class.getResource("client.xml");
@@ -97,10 +111,9 @@ public class JAXRSJweJwsTest extends AbstractBusClientServerTestBase {
                                      "org/apache/cxf/systest/jaxrs/security/bob.jwk.properties");
         bean.getProperties(true).put("rs.security.encryption.in.properties",
                                      "org/apache/cxf/systest/jaxrs/security/alice.jwk.properties");
-        BookStore bs = bean.create(BookStore.class);
-        String text = bs.echoText("book");
-        assertEquals("book", text);
+        return bean.create(BookStore.class);
     }
+    
     @Test
     public void testJweJwkAesWrap() throws Exception {
         String address = "https://localhost:" + PORT + "/jwejwkaeswrap";

http://git-wip-us.apache.org/repos/asf/cxf/blob/d20d5180/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJwsJsonTest.java
----------------------------------------------------------------------
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJwsJsonTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJwsJsonTest.java
index 892ca40..f515da1 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJwsJsonTest.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jwt/JAXRSJwsJsonTest.java
@@ -28,6 +28,7 @@ import org.apache.cxf.bus.spring.SpringBusFactory;
 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.apache.cxf.rs.security.jose.jaxrs.JwsJsonClientResponseFilter;
 import org.apache.cxf.rs.security.jose.jaxrs.JwsJsonWriterInterceptor;
+import org.apache.cxf.systest.jaxrs.security.Book;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 
 import org.junit.BeforeClass;
@@ -43,8 +44,21 @@ public class JAXRSJwsJsonTest extends AbstractBusClientServerTestBase {
     }
     
     @Test
-    public void testJwsJwkHMac() throws Exception {
+    public void testJwsJsonPlainTextHmac() throws Exception {
         String address = "https://localhost:" + PORT + "/jwsjsonhmac";
+        BookStore bs = createBookStore(address, "org/apache/cxf/systest/jaxrs/security/secret.jwk.properties");
+        String text = bs.echoText("book");
+        assertEquals("book", text);
+    }
+    @Test
+    public void testJweJsonBookBeanHmac() throws Exception {
+        String address = "https://localhost:" + PORT + "/jwsjsonhmac";
+        BookStore bs = createBookStore(address, "org/apache/cxf/systest/jaxrs/security/secret.jwk.properties");
+        Book book = bs.echoBook(new Book("book", 123L));
+        assertEquals("book", book.getName());
+        assertEquals(123L, book.getId());
+    }
+    private BookStore createBookStore(String address, String properties) throws Exception {
         JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
         SpringBusFactory bf = new SpringBusFactory();
         URL busFile = JAXRSJwsJsonTest.class.getResource("client.xml");
@@ -53,14 +67,13 @@ public class JAXRSJwsJsonTest extends AbstractBusClientServerTestBase {
         bean.setServiceClass(BookStore.class);
         bean.setAddress(address);
         List<Object> providers = new LinkedList<Object>();
-        providers.add(new JwsJsonWriterInterceptor());
+        JwsJsonWriterInterceptor writer = new JwsJsonWriterInterceptor();
+        writer.setUseJwsJsonOutputStream(true);
+        providers.add(writer);
         providers.add(new JwsJsonClientResponseFilter());
         bean.setProviders(providers);
-        bean.getProperties(true).put("rs.security.signature.list.properties", 
-                                     "org/apache/cxf/systest/jaxrs/security/secret.jwk.properties");
-        BookStore bs = bean.create(BookStore.class);
-        String text = bs.echoText("book");
-        assertEquals("book", text);
+        bean.getProperties(true).put("rs.security.signature.list.properties", properties);
+        return bean.create(BookStore.class);
     }
     
 }