Repository: cxf Updated Branches: refs/heads/3.0.x-fixes 0ed845dbe -> f56f16753
[CXF-6053] Applying a second part of the modified patch on behalf of Daniel Torkian and Luigi Lo Iacono Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/f56f1675 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/f56f1675 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/f56f1675 Branch: refs/heads/3.0.x-fixes Commit: f56f167536fcd72f3fc4b8a92be6617c3b2ddd3c Parents: 0ed845d Author: Sergey Beryozkin <[email protected]> Authored: Mon Oct 20 16:32:57 2014 +0100 Committer: Sergey Beryozkin <[email protected]> Committed: Mon Oct 20 16:34:34 2014 +0100 ---------------------------------------------------------------------- .../rs/security/jose/jws/JwsJsonConsumer.java | 7 +- .../rs/security/jose/jws/JwsJsonProducer.java | 118 +++++++++++++++++++ .../jose/jws/JwsJsonProtectedHeader.java | 13 +- .../jose/jws/JwsJsonSignatureEntry.java | 43 +++++-- .../jose/jws/JwsJsonUnprotectedHeader.java | 10 +- .../security/jose/jws/JwsJsonProducerTest.java | 93 +++++++++++++++ 6 files changed, 251 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java index 45069b3..f22bb89 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java @@ -60,9 +60,6 @@ public class JwsJsonConsumer { @SuppressWarnings("unchecked") Map<String, Object> header = (Map<String, Object>)signatureEntry.get("header"); String signature = (String)signatureEntry.get("signature"); - if (protectedHeader == null && header == null || signature == null) { - throw new SecurityException("Invalid security entry"); - } JwsJsonSignatureEntry signatureObject = new JwsJsonSignatureEntry(encodedJwsPayload, protectedHeader, @@ -97,5 +94,7 @@ public class JwsJsonConsumer { public boolean verifySignatureWith(JsonWebKey key) { return verifySignatureWith(JwsUtils.getSignatureVerifier(key)); } - + public JwsJsonProducer toProducer() { + return new JwsJsonProducer(getDecodedJwsPayload(), signatureEntries); + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducer.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducer.java new file mode 100644 index 0000000..740ded1 --- /dev/null +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducer.java @@ -0,0 +1,118 @@ +/** + * 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.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.rs.security.jose.JoseHeaders; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; +public class JwsJsonProducer { + private String plainPayload; + private String encodedPayload; + private StringBuilder jwsJsonSignedDocBuilder = new StringBuilder(); + private List<JwsJsonSignatureEntry> signatures = new LinkedList<JwsJsonSignatureEntry>(); + public JwsJsonProducer(String tbsDocument) { + this.plainPayload = tbsDocument; + this.encodedPayload = Base64UrlUtility.encode(tbsDocument); + } + public JwsJsonProducer(String tbsDocument, List<JwsJsonSignatureEntry> signatures) { + this(tbsDocument); + for (JwsJsonSignatureEntry entry : signatures) { + updateJwsJsonSignedDocument(entry); + } + } + + public String getPlainPayload() { + return plainPayload; + } + public String getUnsignedEncodedPayload() { + return encodedPayload; + } + + public String getJwsJsonSignedDocument() { + if (signatures.isEmpty()) { + throw new SecurityException("Signature is not available"); + } + return jwsJsonSignedDocBuilder.toString() + "]}"; + } + public List<JwsJsonSignatureEntry> getSignatureEntries() { + return Collections.unmodifiableList(signatures); + } + public String signWith(JwsSignatureProvider signer) { + JoseHeaders headers = new JoseHeaders(); + headers.setAlgorithm(signer.getAlgorithm()); + return signWith(signer, new JwsJsonProtectedHeader(headers)); + } + public String signWith(JwsSignatureProvider signer, + JwsJsonProtectedHeader protectedHeader) { + return signWith(signer, protectedHeader, null); + } + public String signWith(JsonWebKey jwk) { + return signWith(JwsUtils.getSignatureProvider(jwk)); + } + public String signWith(JwsSignatureProvider signer, + JwsJsonProtectedHeader protectedHeader, + JwsJsonUnprotectedHeader unprotectedHeader) { + JoseHeaders unionHeaders = new JoseHeaders(); + + if (protectedHeader != null) { + unionHeaders.asMap().putAll(protectedHeader.getHeaderEntries().asMap()); + } + if (unprotectedHeader != null) { + if (!Collections.disjoint(unionHeaders.asMap().keySet(), + unprotectedHeader.getHeaderEntries().asMap().keySet())) { + throw new SecurityException("Protected and unprotected headers have duplicate values"); + } + unionHeaders.asMap().putAll(unprotectedHeader.getHeaderEntries().asMap()); + } + if (unionHeaders.getAlgorithm() == null) { + throw new SecurityException("Algorithm header is not set"); + } + JwsSignature worker = signer.createJwsSignature(unionHeaders); + String sequenceToBeSigned = protectedHeader.getEncodedHeaderEntries() + + "." + getUnsignedEncodedPayload(); + byte[] bytesToBeSigned = StringUtils.toBytesUTF8(sequenceToBeSigned); + worker.update(bytesToBeSigned, 0, bytesToBeSigned.length); + byte[] signatureBytes = worker.sign(); + String encodedSignatureBytes = Base64UrlUtility.encode(signatureBytes); + JwsJsonSignatureEntry signature = + new JwsJsonSignatureEntry(encodedPayload, + protectedHeader.getEncodedHeaderEntries(), + encodedSignatureBytes, + unprotectedHeader); + return updateJwsJsonSignedDocument(signature); + } + + private String updateJwsJsonSignedDocument(JwsJsonSignatureEntry signature) { + if (signatures.isEmpty()) { + jwsJsonSignedDocBuilder.append("{\"payload\":\"" + encodedPayload + "\""); + jwsJsonSignedDocBuilder.append(",\"signatures\":["); + } else { + jwsJsonSignedDocBuilder.append(","); + } + jwsJsonSignedDocBuilder.append(signature.toJson()); + signatures.add(signature); + + return getJwsJsonSignedDocument(); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/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 81b3bf6..05cecdb 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,17 +49,8 @@ public class JwsJsonProtectedHeader { public Object getHeader(String name) { return headerEntries.getHeader(name); } - public String toJson() { - if (headerEntries == null) { - return ""; - } - //The "protected" member MUST be present and contain the value - // BASE64URL(UTF8(JWS Protected Header)) when the JWS Protected - // Header value is non-empty; otherwise, it MUST be absent. These - // Header Parameter values are integrity protected. - return "\"protected\":\"" - + Base64UrlUtility.encode(writer.headersToJson(headerEntries)) - + "\""; + public String getEncodedHeaderEntries() { + return Base64UrlUtility.encode(writer.headersToJson(headerEntries)); } } http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonSignatureEntry.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonSignatureEntry.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonSignatureEntry.java index 8b73f45..b9f37b5 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonSignatureEntry.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonSignatureEntry.java @@ -33,12 +33,16 @@ public class JwsJsonSignatureEntry { private String encodedSignature; private JwsJsonProtectedHeader protectedHeader; private JwsJsonUnprotectedHeader unprotectedHeader; - private JoseHeaders unionHeader; + private JoseHeaders unionHeaders; public JwsJsonSignatureEntry(String encodedJwsPayload, String encodedProtectedHeader, String encodedSignature, JwsJsonUnprotectedHeader unprotectedHeader) { + if (encodedProtectedHeader == null && unprotectedHeader == null || encodedSignature == null) { + throw new SecurityException("Invalid security entry"); + } + this.encodedJwsPayload = encodedJwsPayload; this.encodedProtectedHeader = encodedProtectedHeader; this.encodedSignature = encodedSignature; @@ -48,14 +52,18 @@ public class JwsJsonSignatureEntry { prepare(); } private void prepare() { - JoseHeaders joseProtectedHeader = getProtectedHeader().getHeaderEntries(); - JoseHeaders joseUnprotectedHeader = getUnprotectedHeader().getHeaderEntries(); - if (!Collections.disjoint(joseProtectedHeader.asMap().keySet(), - joseUnprotectedHeader.asMap().keySet())) { - throw new SecurityException("Protected and unprotected headers have duplicate values"); + unionHeaders = new JoseHeaders(); + + if (protectedHeader != null) { + unionHeaders.asMap().putAll(protectedHeader.getHeaderEntries().asMap()); + } + if (unprotectedHeader != null) { + if (!Collections.disjoint(unionHeaders.asMap().keySet(), + unprotectedHeader.getHeaderEntries().asMap().keySet())) { + throw new SecurityException("Protected and unprotected headers have duplicate values"); + } + unionHeaders.asMap().putAll(unprotectedHeader.getHeaderEntries().asMap()); } - unionHeader = new JoseHeaders(joseProtectedHeader); - unionHeader.asMap().putAll(joseUnprotectedHeader.asMap()); } public String getEncodedJwsPayload() { return encodedJwsPayload; @@ -76,7 +84,7 @@ public class JwsJsonSignatureEntry { return unprotectedHeader; } public JoseHeaders getUnionHeader() { - return unionHeader; + return unionHeaders; } public String getEncodedSignature() { return encodedSignature; @@ -100,4 +108,21 @@ public class JwsJsonSignatureEntry { public boolean verifySignatureWith(JsonWebKey key) { return verifySignatureWith(JwsUtils.getSignatureVerifier(key)); } + public String toJson() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + if (protectedHeader != null) { + sb.append("\"protected\":\"" + protectedHeader.getEncodedHeaderEntries() + "\""); + } + if (unprotectedHeader != null) { + if (protectedHeader != null) { + sb.append(","); + } + sb.append("\"header\":\"" + unprotectedHeader.toJson() + "\""); + } + sb.append(","); + sb.append("\"signature\":\"" + encodedSignature + "\""); + sb.append("}"); + return sb.toString(); + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonUnprotectedHeader.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonUnprotectedHeader.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonUnprotectedHeader.java index 7fdd54a..90a7d1f 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonUnprotectedHeader.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonUnprotectedHeader.java @@ -53,14 +53,6 @@ public class JwsJsonUnprotectedHeader { return headerEntries; } public String toJson() { - // The "header" member MUST be present and contain the value JWS - // Unprotected Header when the JWS Unprotected Header value is non- - // empty; otherwise, it MUST be absent. This value is represented as - // an unencoded JSON object, rather than as a string. These Header - // Parameter values are not integrity protected. - if (headerEntries == null) { - return ""; - } - return "\"header\":" + writer.headersToJson(headerEntries); + return writer.headersToJson(headerEntries); } } http://git-wip-us.apache.org/repos/asf/cxf/blob/f56f1675/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducerTest.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducerTest.java b/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducerTest.java new file mode 100644 index 0000000..d6a5fd0 --- /dev/null +++ b/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jws/JwsJsonProducerTest.java @@ -0,0 +1,93 @@ +/** + * 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 org.apache.cxf.rs.security.jose.JoseConstants; +import org.apache.cxf.rs.security.jose.JoseHeaders; + +import org.junit.Assert; +import org.junit.Test; + +public class JwsJsonProducerTest extends Assert { + + public static final String ENCODED_MAC_KEY_1 = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75" + + "aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"; + + public static final String ENCODED_MAC_KEY_2 = "09Y_RK7l5rAY9QY7EblYQNuYbu9cy1j7ovCbkeIyAKN8LIeRL-3H8g" + + "c8kZSYzAQ1uTRC_egZ_8cgZSZa9T5nmQ"; + + public static final String UNSIGNED_PLAIN_JSON_DOCUMENT = "{" + + " \"from\": \"user\"," + " \"to\": \"developer\"," + + " \"msg\": \"good job!\" " + "}"; + + public static final String UNSIGNED_PLAIN_JSON_DOCUMENT_AS_B64URL = "eyAiZnJvbSI6ICJ1c2VyIiwgInRvIjogI" + + "mRldmVsb3BlciIsICJtc2ciOiAiZ29vZCBqb2IhIiB9"; + + + public static final String SIGNED_JWS_JSON_DOCUMENT = "{" + + "\"payload\":\"" + + UNSIGNED_PLAIN_JSON_DOCUMENT_AS_B64URL + + "\",\"signatures\":[{\"protected\":\"eyJhbGciOiJIUzI1NiJ9\",\"signature\":" + + "\"NNksREOsFCI1nUQEqzCe6XZFa-bRAge2XXMMAU2Jj2I\"}]}"; + + public static final String DUAL_SIGNED_JWS_JSON_DOCUMENT = "{" + + "\"payload\":\"" + + UNSIGNED_PLAIN_JSON_DOCUMENT_AS_B64URL + + "\",\"signatures\":[{\"protected\":\"eyJhbGciOiJIUzI1NiJ9\"," + + "\"signature\":\"NNksREOsFCI1nUQEqzCe6XZFa-bRAge2XXMMAU2Jj2I\"}," + + "{\"protected\":\"eyJhbGciOiJIUzI1NiJ9\"," + + "\"signature\":\"KY2r_Gubar7G86fVyrA7I2-69KA7faKDmebfCCmibdI\"}]}"; + + @Test + public void testSignPlainJsonDocumentPayloadConstruction() { + JwsJsonProducer producer = new JwsJsonProducer(UNSIGNED_PLAIN_JSON_DOCUMENT); + + assertEquals(UNSIGNED_PLAIN_JSON_DOCUMENT_AS_B64URL, + producer.getUnsignedEncodedPayload()); + } + + + @Test + public void testSignWithProtectedHeaderOnly() { + JwsJsonProducer producer = new JwsJsonProducer(UNSIGNED_PLAIN_JSON_DOCUMENT); + JoseHeaders headerEntries = new JoseHeaders(); + headerEntries.setAlgorithm(JoseConstants.HMAC_SHA_256_ALGO); + + producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_1, + JoseConstants.HMAC_SHA_256_ALGO), + new JwsJsonProtectedHeader(headerEntries)); + assertEquals(SIGNED_JWS_JSON_DOCUMENT, + producer.getJwsJsonSignedDocument()); + } + @Test + public void testDualSignWithProtectedHeaderOnly() { + JwsJsonProducer producer = new JwsJsonProducer(UNSIGNED_PLAIN_JSON_DOCUMENT); + JoseHeaders headerEntries = new JoseHeaders(); + headerEntries.setAlgorithm(JoseConstants.HMAC_SHA_256_ALGO); + + producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_1, + JoseConstants.HMAC_SHA_256_ALGO), + new JwsJsonProtectedHeader(headerEntries)); + producer.signWith(new HmacJwsSignatureProvider(ENCODED_MAC_KEY_2, + JoseConstants.HMAC_SHA_256_ALGO), + new JwsJsonProtectedHeader(headerEntries)); + assertEquals(DUAL_SIGNED_JWS_JSON_DOCUMENT, + producer.getJwsJsonSignedDocument()); + } + +}
