Return-Path: X-Original-To: apmail-cxf-commits-archive@www.apache.org Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 058F217932 for ; Fri, 31 Oct 2014 15:38:02 +0000 (UTC) Received: (qmail 91876 invoked by uid 500); 31 Oct 2014 15:38:01 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 91826 invoked by uid 500); 31 Oct 2014 15:38:01 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 91816 invoked by uid 99); 31 Oct 2014 15:38:01 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 31 Oct 2014 15:38:01 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 88B7C8B89A7; Fri, 31 Oct 2014 15:38:01 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sergeyb@apache.org To: commits@cxf.apache.org Message-Id: <607868e52ade4ed6b8e58096e2e132ef@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: git commit: [CXF-6053] Prototyping JwSJsonOutputStream and supporting multiple key aliases in a single properties file Date: Fri, 31 Oct 2014 15:38:01 +0000 (UTC) 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 Authored: Fri Oct 31 15:37:43 2014 +0000 Committer: Sergey Beryozkin 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 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 theSigVerifiers = new LinkedList(); 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 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 theSigProviders = new LinkedList(); 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 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 protectedHeaders = new ArrayList(sigProviders.size()); + List signatures = new ArrayList(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 keys = jwkSet.getKeyUseMap().get(keyOper); + if (keys != null && keys.size() == 1) { + return keys.get(0); } } + return null; + } + public static List loadJsonWebKeys(Message m, Properties props, String keyOper) { + return loadJsonWebKeys(m, props, keyOper, new DefaultJwkReaderWriter()); + } + + public static List 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 keys = new ArrayList(values.length); + for (String value : values) { + keys.add(jwkSet.getKey(value)); + } + return keys; + } + if (keyOper != null) { + List 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 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 protectedHeaders; + private List signatures; + private ExecutorService executor; + public JwsJsonOutputStream(OutputStream out, + List protectedHeaders, + List 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 loadSignatureProviders(String propLoc, Message m) { + Properties props = loadProperties(m, propLoc); + JwsSignatureProvider theSigProvider = loadSignatureProvider(propLoc, m, true); + if (theSigProvider != null) { + return Collections.singletonList(theSigProvider); + } + List theSigProviders = null; + if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) { + List jwks = JwkUtils.loadJsonWebKeys(m, props, JsonWebKey.KEY_OPER_SIGN); + if (jwks != null) { + theSigProviders = new ArrayList(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 loadSignatureVerifiers(String propLoc, Message m) { + Properties props = loadProperties(m, propLoc); + JwsSignatureVerifier theVerifier = loadSignatureVerifier(propLoc, m, true); + if (theVerifier != null) { + return Collections.singletonList(theVerifier); + } + List theVerifiers = null; + if (JwkUtils.JWK_KEY_STORE_TYPE.equals(props.get(KeyManagementUtils.RSSEC_KEY_STORE_TYPE))) { + List jwks = JwkUtils.loadJsonWebKeys(m, props, JsonWebKey.KEY_OPER_SIGN); + if (jwks != null) { + theVerifiers = new ArrayList(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 providers = new LinkedList(); - 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); } }