Repository: cxf Updated Branches: refs/heads/3.0.x-fixes 6c6acecbc -> bcec16a62
[CXF-6085] Prototyping the actual JweJson producer code, using the patch from Daniel Torkian as the input Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/bcec16a6 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/bcec16a6 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/bcec16a6 Branch: refs/heads/3.0.x-fixes Commit: bcec16a62c6f0ec665a93f3e3960761fc41dedff Parents: 6c6acec Author: Sergey Beryozkin <sberyoz...@talend.com> Authored: Tue Feb 10 13:34:31 2015 +0000 Committer: Sergey Beryozkin <sberyoz...@talend.com> Committed: Tue Feb 10 13:36:16 2015 +0000 ---------------------------------------------------------------------- .../jose/jaxrs/JweWriterInterceptor.java | 4 +- .../jose/jwe/AbstractJweEncryption.java | 39 +++-- .../security/jose/jwe/JweEncryptionInput.java | 60 +++++++ .../jose/jwe/JweEncryptionProvider.java | 8 +- .../jose/jwe/JweJsonEncryptionEntry.java | 56 +++++++ .../rs/security/jose/jwe/JweJsonProducer.java | 168 +++++++++++++++++++ 6 files changed, 314 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java index ca4f5c9..7291edd 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JweWriterInterceptor.java @@ -35,6 +35,7 @@ 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.jwe.JweCompactProducer; +import org.apache.cxf.rs.security.jose.jwe.JweEncryptionInput; import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider; import org.apache.cxf.rs.security.jose.jwe.JweEncryptionState; import org.apache.cxf.rs.security.jose.jwe.JweHeaders; @@ -70,7 +71,8 @@ public class JweWriterInterceptor implements WriterInterceptor { } if (useJweOutputStream) { - JweEncryptionState encryption = theEncryptionProvider.createJweEncryptionState(jweHeaders, null); + JweEncryptionState encryption = + theEncryptionProvider.createJweEncryptionState(new JweEncryptionInput(jweHeaders)); try { JweCompactProducer.startJweContent(actualOs, encryption.getHeaders(), http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java index 9103ecf..e0d7cf6 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/AbstractJweEncryption.java @@ -107,8 +107,8 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider { return writer; } @Override - public JweEncryptionState createJweEncryptionState(JweHeaders jweHeaders, byte[] aad) { - JweEncryptionInternal state = getInternalState(jweHeaders, aad); + public JweEncryptionState createJweEncryptionState(JweEncryptionInput jweInput) { + JweEncryptionInternal state = getInternalState(jweInput.getJweHeaders(), jweInput); Cipher c = CryptoUtils.initCipher(createCekSecretKey(state), state.keyProps, Cipher.ENCRYPT_MODE); return new JweEncryptionState(c, @@ -130,7 +130,7 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider { return theCek; } - private JweEncryptionInternal getInternalState(JweHeaders jweHeaders, byte[] aad) { + private JweEncryptionInternal getInternalState(JweHeaders jweInHeaders, JweEncryptionInput jweInput) { JweHeaders theHeaders = new JweHeaders(); if (getKeyAlgorithm() != null) { theHeaders.setKeyEncryptionAlgorithm(getKeyAlgorithm()); @@ -138,33 +138,35 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider { theHeaders.setContentEncryptionAlgorithm(getContentAlgorithm()); JweHeaders protectedHeaders = null; - if (jweHeaders != null) { - if (jweHeaders.getKeyEncryptionAlgorithm() != null + if (jweInHeaders != null) { + if (jweInHeaders.getKeyEncryptionAlgorithm() != null && (getKeyAlgorithm() == null - || !getKeyAlgorithm().equals(jweHeaders.getKeyEncryptionAlgorithm())) - || jweHeaders.getAlgorithm() != null - && !getContentAlgorithm().equals(jweHeaders.getAlgorithm())) { + || !getKeyAlgorithm().equals(jweInHeaders.getKeyEncryptionAlgorithm())) + || jweInHeaders.getAlgorithm() != null + && !getContentAlgorithm().equals(jweInHeaders.getAlgorithm())) { throw new SecurityException(); } - theHeaders.asMap().putAll(jweHeaders.asMap()); - if (jweHeaders.getProtectedHeaders() != null - && !jweHeaders.asMap().entrySet().containsAll(theHeaders.asMap().entrySet())) { - jweHeaders.getProtectedHeaders().asMap().putAll(theHeaders.asMap()); + theHeaders.asMap().putAll(jweInHeaders.asMap()); + if (jweInHeaders.getProtectedHeaders() != null + && !jweInHeaders.asMap().entrySet().containsAll(theHeaders.asMap().entrySet())) { + jweInHeaders.getProtectedHeaders().asMap().putAll(theHeaders.asMap()); } - protectedHeaders = jweHeaders.getProtectedHeaders() != null - ? jweHeaders.getProtectedHeaders() : theHeaders; + protectedHeaders = jweInHeaders.getProtectedHeaders() != null + ? jweInHeaders.getProtectedHeaders() : theHeaders; } else { protectedHeaders = theHeaders; } - byte[] theCek = getContentEncryptionKey(theHeaders); + byte[] theCek = jweInput != null && jweInput.getCek() != null + ? jweInput.getCek() : getContentEncryptionKey(theHeaders); String contentEncryptionAlgoJavaName = Algorithm.toJavaName(getContentEncryptionAlgoJwt()); KeyProperties keyProps = new KeyProperties(contentEncryptionAlgoJavaName); keyProps.setCompressionSupported(compressionRequired(theHeaders)); - byte[] theIv = getContentEncryptionAlgorithm().getInitVector(); + byte[] theIv = jweInput != null && jweInput.getIv() != null + ? jweInput.getIv() : getContentEncryptionAlgorithm().getInitVector(); AlgorithmParameterSpec specParams = getAlgorithmParameterSpec(theIv); keyProps.setAlgoSpec(specParams); byte[] jweContentEncryptionKey = @@ -173,7 +175,8 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider { String protectedHeadersJson = writer.headersToJson(protectedHeaders); - byte[] additionalEncryptionParam = getAAD(protectedHeadersJson, aad); + byte[] additionalEncryptionParam = getAAD(protectedHeadersJson, + jweInput == null ? null : jweInput.getAad()); keyProps.setAdditionalData(additionalEncryptionParam); JweEncryptionInternal state = new JweEncryptionInternal(); @@ -183,7 +186,7 @@ public abstract class AbstractJweEncryption implements JweEncryptionProvider { state.secretKey = theCek; state.theIv = theIv; state.protectedHeadersJson = protectedHeadersJson; - state.aad = aad; + state.aad = jweInput != null ? jweInput.getAad() : null; return state; } private boolean compressionRequired(JweHeaders theHeaders) { http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java new file mode 100644 index 0000000..cb07be3 --- /dev/null +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionInput.java @@ -0,0 +1,60 @@ +/** + * 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; + +public class JweEncryptionInput { + private JweHeaders jweHeaders; + private byte[] cek; + private byte[] iv; + private byte[] aad; + public JweEncryptionInput(JweHeaders jweHeaders) { + this.jweHeaders = jweHeaders; + } + public JweEncryptionInput(JweHeaders jweHeaders, + byte[] cek, + byte[] iv) { + this(jweHeaders, cek, iv, null); + + } + public JweEncryptionInput(JweHeaders jweHeaders, + byte[] aad) { + this(jweHeaders, null, null, aad); + } + public JweEncryptionInput(JweHeaders jweHeaders, + byte[] cek, + byte[] iv, + byte[] aad) { + this(jweHeaders); + this.cek = cek; + this.iv = iv; + this.aad = aad; + } + public JweHeaders getJweHeaders() { + return jweHeaders; + } + public byte[] getCek() { + return cek; + } + public byte[] getIv() { + return iv; + } + public byte[] getAad() { + return aad; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java index df4a0d9..25b931a 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweEncryptionProvider.java @@ -21,9 +21,13 @@ package org.apache.cxf.rs.security.jose.jwe; public interface JweEncryptionProvider extends JweKeyProperties { + /** + * JWE compact encryption + */ String encrypt(byte[] jweContent, JweHeaders jweHeaders); /** - * Prepare JWE state (optional operation) + * Prepare JWE state for completing either + * JWE compact or JSON encryption */ - JweEncryptionState createJweEncryptionState(JweHeaders jweHeaders, byte[] aad); + JweEncryptionState createJweEncryptionState(JweEncryptionInput jweInput); } http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java new file mode 100644 index 0000000..cdba53e --- /dev/null +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonEncryptionEntry.java @@ -0,0 +1,56 @@ +/** + * 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.util.LinkedHashMap; +import java.util.Map; + +import org.apache.cxf.jaxrs.provider.json.JsonMapObjectReaderWriter; + +public class JweJsonEncryptionEntry { + private JweHeaders unprotectedHeader; + private String encodedEncryptedKey; + public JweJsonEncryptionEntry(String encodedEncryptedKey) { + this(null, encodedEncryptedKey); + } + public JweJsonEncryptionEntry(JweHeaders unprotectedHeader, String encodedEncryptedKey) { + this.unprotectedHeader = unprotectedHeader; + this.encodedEncryptedKey = encodedEncryptedKey; + } + public JweHeaders getUnprotectedHeader() { + return unprotectedHeader; + } + public String getEncodedEncryptedKey() { + return encodedEncryptedKey; + } + public String toJson() { + JsonMapObjectReaderWriter jsonWriter = new JsonMapObjectReaderWriter(); + Map<String, Object> recipientsEntry = new LinkedHashMap<String, Object>(); + if (unprotectedHeader != null) { + recipientsEntry.put("header", this.unprotectedHeader); + } + if (encodedEncryptedKey != null) { + recipientsEntry.put("encrypted_key", this.encodedEncryptedKey); + } + return jsonWriter.toJson(recipientsEntry); + } + public String toString() { + return toJson(); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/bcec16a6/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java new file mode 100644 index 0000000..8879e24 --- /dev/null +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweJsonProducer.java @@ -0,0 +1,168 @@ +/** + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.crypto.CryptoUtils; +import org.apache.cxf.rs.security.jose.JoseHeadersReaderWriter; + +public class JweJsonProducer { + private JoseHeadersReaderWriter writer = new JoseHeadersReaderWriter(); + private JweHeaders protectedHeader; + private JweHeaders unprotectedHeader; + private byte[] content; + private byte[] aad; + public JweJsonProducer(JweHeaders protectedHeader, byte[] content) { + this.protectedHeader = protectedHeader; + this.content = content; + } + public JweJsonProducer(JweHeaders protectedHeader, byte[] content, byte[] aad) { + this(protectedHeader, content); + this.aad = aad; + } + public JweJsonProducer(JweHeaders protectedHeader, JweHeaders unprotectedHeader, + byte[] content, byte[] aad) { + this(protectedHeader, content, aad); + this.unprotectedHeader = unprotectedHeader; + } + + public String encryptWith(List<JweEncryptionProvider> encryptors) { + return encryptWith(encryptors, null); + } + public String encryptWith(List<JweEncryptionProvider> encryptors, + List<JweHeaders> recepientUnprotected) { + checkAndGetContentAlgorithm(encryptors); + if (recepientUnprotected != null + && recepientUnprotected.size() != encryptors.size()) { + throw new IllegalArgumentException(); + } + //TODO: determine the actual cek and iv length based on the algo + byte[] cek = CryptoUtils.generateSecureRandomBytes(32); + byte[] iv = CryptoUtils.generateSecureRandomBytes(16); + JweHeaders unionHeaders = new JweHeaders(); + unionHeaders.setProtectedHeaders(protectedHeader); + if (protectedHeader != null) { + unionHeaders.asMap().putAll(protectedHeader.asMap()); + } + if (unprotectedHeader != null) { + if (!Collections.disjoint(unionHeaders.asMap().keySet(), + unprotectedHeader.asMap().keySet())) { + throw new SecurityException("Protected and unprotected headers have duplicate values"); + } + unionHeaders.asMap().putAll(unprotectedHeader.asMap()); + } + + List<JweJsonEncryptionEntry> entries = new ArrayList<JweJsonEncryptionEntry>(encryptors.size()); + Map<String, Object> jweJsonMap = new LinkedHashMap<String, Object>(); + if (protectedHeader != null) { + jweJsonMap.put("protected", + Base64UrlUtility.encode(writer.toJson(protectedHeader))); + } + if (unprotectedHeader != null) { + jweJsonMap.put("unprotected", unprotectedHeader); + } + byte[] cipherText = null; + byte[] authTag = null; + for (int i = 0; i < encryptors.size(); i++) { + JweEncryptionProvider encryptor = encryptors.get(i); + JweHeaders perRecipientUnprotected = + recepientUnprotected == null ? null : recepientUnprotected.get(i); + JweHeaders jsonHeaders = null; + if (recepientUnprotected != null) { + if (!Collections.disjoint(unionHeaders.asMap().keySet(), + perRecipientUnprotected.asMap().keySet())) { + throw new SecurityException("Protected and unprotected headers have duplicate values"); + } + jsonHeaders = new JweHeaders(unionHeaders.asMap()); + jsonHeaders.asMap().putAll(unprotectedHeader.asMap()); + } else { + jsonHeaders = null; + } + JweEncryptionInput input = new JweEncryptionInput(unionHeaders, + cek, + iv, + aad); + + JweEncryptionState state = encryptor.createJweEncryptionState(input); + try { + byte[] currentCipherOutput = state.getCipher().doFinal(content); + byte[] currentCipherText = null; + byte[] currentAuthTag = null; + if (state.getAuthTagProducer() != null) { + currentCipherText = currentCipherOutput; + state.getAuthTagProducer().update(content, 0, content.length); + currentAuthTag = state.getAuthTagProducer().getTag(); + } else { + final int authTagLengthBits = 128; + final int cipherTextLen = currentCipherOutput.length - authTagLengthBits / 8; + currentCipherText = Arrays.copyOf(currentCipherOutput, cipherTextLen); + currentAuthTag = Arrays.copyOfRange(currentCipherOutput, cipherTextLen, authTagLengthBits / 8); + if (cipherText == null) { + cipherText = currentCipherText; + } else if (!Arrays.equals(cipherText, currentCipherText)) { + throw new SecurityException(); + } + if (authTag == null) { + authTag = currentAuthTag; + } else if (!Arrays.equals(authTag, currentAuthTag)) { + throw new SecurityException(); + } + } + + byte[] encryptedCek = state.getContentEncryptionKey(); + if (encryptedCek == null && encryptor.getKeyAlgorithm() != null) { + // can be null only if it is the direct key encryption + throw new SecurityException(); + } + String encodedCek = encryptedCek == null ? null : Base64UrlUtility.encode(encryptedCek); + entries.add(new JweJsonEncryptionEntry(perRecipientUnprotected, encodedCek)); + } catch (Exception ex) { + throw new SecurityException(ex); + } + } + jweJsonMap.put("recipients", entries); + if (aad != null) { + jweJsonMap.put("aad", Base64UrlUtility.encode(aad)); + } + jweJsonMap.put("iv", Base64UrlUtility.encode(iv)); + jweJsonMap.put("ciphertext", Base64UrlUtility.encode(cipherText)); + jweJsonMap.put("tag", Base64UrlUtility.encode(authTag)); + return writer.toJson(jweJsonMap); + } + private String checkAndGetContentAlgorithm(List<JweEncryptionProvider> encryptors) { + Set<String> set = new HashSet<String>(); + for (JweEncryptionProvider encryptor : encryptors) { + set.add(encryptor.getContentAlgorithm()); + } + if (set.size() != 1) { + throw new SecurityException("Invalid content encryption algorithm"); + } + return set.iterator().next(); + } + +}