Repository: cxf Updated Branches: refs/heads/master 7454e5a34 -> 27e80bbff
[CXF-5311] Initial attempt at JWE encrypting directly into output stream, cleanup to follow Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/27e80bbf Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/27e80bbf Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/27e80bbf Branch: refs/heads/master Commit: 27e80bbffacc5d119d53f203ed1044a406969497 Parents: 7454e5a Author: Sergey Beryozkin <[email protected]> Authored: Wed Jun 25 16:53:53 2014 +0100 Committer: Sergey Beryozkin <[email protected]> Committed: Wed Jun 25 16:53:53 2014 +0100 ---------------------------------------------------------------------- .../oauth2/jwe/AbstractJweEncryptor.java | 79 +++++++---- .../security/oauth2/jwe/JweCompactProducer.java | 57 ++++---- .../rs/security/oauth2/jwe/JweEncryptor.java | 2 +- .../oauth2/jwe/JweEncryptorWorkerState.java | 43 ++++++ .../rs/security/oauth2/jwe/JweOutputStream.java | 133 +++++++++++++++++++ .../oauth2/jwe/WrappedKeyJweEncryptor.java | 7 + .../oauth2/jwt/jaxrs/JweWriterInterceptor.java | 31 ++++- .../oauth2/jwe/JweCompactReaderWriterTest.java | 6 +- .../oauth2/utils/crypto/CryptoUtils.java | 6 +- .../jaxrs/security/jwt/JAXRSJweJwsTest.java | 4 +- 10 files changed, 307 insertions(+), 61 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/AbstractJweEncryptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/AbstractJweEncryptor.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/AbstractJweEncryptor.java index 64e29da..0835836 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/AbstractJweEncryptor.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/AbstractJweEncryptor.java @@ -18,9 +18,10 @@ */ package org.apache.cxf.rs.security.oauth2.jwe; -import java.io.UnsupportedEncodingException; import java.security.spec.AlgorithmParameterSpec; +import java.util.concurrent.atomic.AtomicInteger; +import javax.crypto.Cipher; import javax.crypto.SecretKey; import org.apache.cxf.rs.security.oauth2.jwt.Algorithm; @@ -36,6 +37,7 @@ public abstract class AbstractJweEncryptor implements JweEncryptor { private JwtHeadersWriter writer = new JwtTokenReaderWriter(); private byte[] cek; private byte[] iv; + private AtomicInteger providedIvUsageCount; private int authTagLen = DEFAULT_AUTH_TAG_LENGTH; protected AbstractJweEncryptor(SecretKey cek, byte[] iv) { @@ -47,6 +49,9 @@ public abstract class AbstractJweEncryptor implements JweEncryptor { this.headers = headers; this.cek = cek; this.iv = iv; + if (iv != null && iv.length > 0) { + providedIvUsageCount = new AtomicInteger(); + } } protected AbstractJweEncryptor(JweHeaders headers, byte[] cek, byte[] iv, int authTagLen) { this(headers, cek, iv); @@ -68,7 +73,13 @@ public abstract class AbstractJweEncryptor implements JweEncryptor { } protected byte[] getContentEncryptionCipherInitVector() { - return iv == null ? CryptoUtils.generateSecureRandomBytes(DEFAULT_IV_SIZE) : iv; + if (iv == null) { + return CryptoUtils.generateSecureRandomBytes(DEFAULT_IV_SIZE); + } else if (iv.length > 0 && providedIvUsageCount.addAndGet(1) > 1) { + throw new SecurityException(); + } else { + return iv; + } } protected byte[] getContentEncryptionKey() { @@ -91,6 +102,35 @@ public abstract class AbstractJweEncryptor implements JweEncryptor { return headers; } public String encrypt(byte[] content, String contentType) { + JweEncryptorInternalState state = getInternalState(contentType); + + byte[] cipherText = CryptoUtils.encryptBytes( + content, + state.secretKey, + state.keyProps); + + + JweCompactProducer producer = new JweCompactProducer(state.theHeaders, + writer, + state.jweContentEncryptionKey, + state.theIv, + cipherText, + getAuthTagLen()); + return producer.getJweContent(); + } + + public JweEncryptorWorkerState newWorkerState(String contentType) { + JweEncryptorInternalState state = getInternalState(contentType); + String jweStart = JweCompactProducer.startJweContent(state.theHeaders, + writer, + state.jweContentEncryptionKey, + state.theIv); + Cipher c = CryptoUtils.initCipher(state.secretKey, state.keyProps, + Cipher.ENCRYPT_MODE); + return new JweEncryptorWorkerState(jweStart, c, getAuthTagLen()); + } + + private JweEncryptorInternalState getInternalState(String contentType) { JweHeaders theHeaders = headers; if (contentType != null) { theHeaders = new JweHeaders(theHeaders.asMap()); @@ -106,29 +146,22 @@ public abstract class AbstractJweEncryptor implements JweEncryptor { byte[] theIv = getContentEncryptionCipherInitVector(); AlgorithmParameterSpec specParams = getContentEncryptionCipherSpec(theIv); keyProps.setAlgoSpec(specParams); - - byte[] cipherText = CryptoUtils.encryptBytes( - content, - CryptoUtils.createSecretKeySpec(theCek, contentEncryptionAlgoJavaName), - keyProps); - byte[] jweContentEncryptionKey = getEncryptedContentEncryptionKey(theCek); - JweCompactProducer producer = new JweCompactProducer(theHeaders, - writer, - jweContentEncryptionKey, - theIv, - cipherText, - getAuthTagLen()); - return producer.getJweContent(); + JweEncryptorInternalState state = new JweEncryptorInternalState(); + state.theHeaders = theHeaders; + state.jweContentEncryptionKey = jweContentEncryptionKey; + state.keyProps = keyProps; + state.secretKey = CryptoUtils.createSecretKeySpec(theCek, + contentEncryptionAlgoJavaName); + state.theIv = theIv; + return state; } - public String encryptText(String text, String contentType) { - try { - return encrypt(text.getBytes("UTF-8"), contentType); - } catch (UnsupportedEncodingException ex) { - throw new SecurityException(ex); - } + private static class JweEncryptorInternalState { + JweHeaders theHeaders; + byte[] jweContentEncryptionKey; + byte[] theIv; + KeyProperties keyProps; + SecretKey secretKey; } - - } http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactProducer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactProducer.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactProducer.java index 82945b7..bb0a24d 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactProducer.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactProducer.java @@ -25,9 +25,7 @@ import org.apache.cxf.rs.security.oauth2.utils.Base64UrlUtility; public class JweCompactProducer { - private String encodedHeaders; - private String encodedContentEncryptionKey; - private String encodedInitVector; + private StringBuilder jweContentBuilder; private String encodedEncryptedContent; private String encodedAuthTag; public JweCompactProducer(JweHeaders headers, @@ -44,10 +42,12 @@ public class JweCompactProducer { byte[] encryptedContentEncryptionKey, byte[] cipherInitVector, byte[] encryptedContentNoTag, - byte[] authenticationTag) { + byte[] authenticationTag) { + jweContentBuilder = startJweContent(new StringBuilder(), headers, writer, + encryptedContentEncryptionKey, cipherInitVector); this.encodedEncryptedContent = Base64UrlUtility.encode(encryptedContentNoTag); this.encodedAuthTag = Base64UrlUtility.encode(authenticationTag); - finalizeInit(headers, writer, encryptedContentEncryptionKey, cipherInitVector); + } public JweCompactProducer(JweHeaders headers, @@ -63,7 +63,9 @@ public class JweCompactProducer { byte[] encryptedContentEncryptionKey, byte[] cipherInitVector, byte[] encryptedContentWithTag, - int authTagLengthBits) { + int authTagLengthBits) { + jweContentBuilder = startJweContent(new StringBuilder(), headers, writer, + encryptedContentEncryptionKey, cipherInitVector); this.encodedEncryptedContent = Base64UrlUtility.encodeChunk( encryptedContentWithTag, 0, @@ -71,29 +73,36 @@ public class JweCompactProducer { this.encodedAuthTag = Base64UrlUtility.encodeChunk( encryptedContentWithTag, encryptedContentWithTag.length - authTagLengthBits / 8, - encryptedContentWithTag.length); - finalizeInit(headers, writer, encryptedContentEncryptionKey, cipherInitVector); + authTagLengthBits / 8); + } - - private void finalizeInit(JweHeaders headers, - JwtHeadersWriter writer, - byte[] encryptedContentEncryptionKey, - byte[] cipherInitVector) { + public static String startJweContent(JweHeaders headers, + JwtHeadersWriter writer, + byte[] encryptedContentEncryptionKey, + byte[] cipherInitVector) { + return startJweContent(new StringBuilder(), + headers, writer, encryptedContentEncryptionKey, cipherInitVector).toString(); + } + public static StringBuilder startJweContent(StringBuilder sb, + JweHeaders headers, + JwtHeadersWriter writer, + byte[] encryptedContentEncryptionKey, + byte[] cipherInitVector) { writer = writer == null ? new JwtTokenReaderWriter() : writer; - this.encodedHeaders = Base64UrlUtility.encode(writer.headersToJson(headers)); - this.encodedContentEncryptionKey = Base64UrlUtility.encode(encryptedContentEncryptionKey); - this.encodedInitVector = Base64UrlUtility.encode(cipherInitVector); + String encodedHeaders = Base64UrlUtility.encode(writer.headersToJson(headers)); + String encodedContentEncryptionKey = Base64UrlUtility.encode(encryptedContentEncryptionKey); + String encodedInitVector = Base64UrlUtility.encode(cipherInitVector); + sb.append(encodedHeaders) + .append('.') + .append(encodedContentEncryptionKey == null ? "" : encodedContentEncryptionKey) + .append('.') + .append(encodedInitVector == null ? "" : encodedInitVector) + .append('.'); + return sb; } public String getJweContent() { - StringBuilder sb = new StringBuilder(); - return sb.append(encodedHeaders) - .append('.') - .append(encodedContentEncryptionKey == null ? "" : encodedContentEncryptionKey) - .append('.') - .append(encodedInitVector == null ? "" : encodedInitVector) - .append('.') - .append(encodedEncryptedContent) + return jweContentBuilder.append(encodedEncryptedContent) .append('.') .append(encodedAuthTag) .toString(); http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptor.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptor.java index f8eb013..b2acb17 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptor.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptor.java @@ -21,5 +21,5 @@ package org.apache.cxf.rs.security.oauth2.jwe; public interface JweEncryptor { String encrypt(byte[] jweContent, String contentType); - String encryptText(String jweContent, String contentType); + JweEncryptorWorkerState newWorkerState(String contentType); } http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptorWorkerState.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptorWorkerState.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptorWorkerState.java new file mode 100644 index 0000000..e73cdf5 --- /dev/null +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweEncryptorWorkerState.java @@ -0,0 +1,43 @@ +/** + * 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.oauth2.jwe; + +import javax.crypto.Cipher; + +public class JweEncryptorWorkerState { + private String jweContentStart; + private Cipher encryptingCipher; + private int authTagLengthBits; + + public JweEncryptorWorkerState(String jweContentStart, Cipher encryptingCipher, int authTagLengthBits) { + this.jweContentStart = jweContentStart; + this.encryptingCipher = encryptingCipher; + this.authTagLengthBits = authTagLengthBits; + } + public Cipher getEncryptingCipher() { + return encryptingCipher; + } + public int getAuthTagLengthBits() { + return authTagLengthBits; + } + public String getJweContentStart() { + return jweContentStart; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweOutputStream.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweOutputStream.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweOutputStream.java new file mode 100644 index 0000000..babe28d --- /dev/null +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/JweOutputStream.java @@ -0,0 +1,133 @@ +/** + * 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.oauth2.jwe; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import javax.crypto.Cipher; + +import org.apache.cxf.rs.security.oauth2.utils.Base64UrlUtility; + +public class JweOutputStream extends FilterOutputStream { + private Cipher encryptingCipher; + private int blockSize; + private int authTagLengthBits; + private byte[] lastRawDataChunk; + private byte[] lastEncryptedDataChunk; + private boolean flushed; + public JweOutputStream(OutputStream out, JweEncryptorWorkerState state) throws IOException { + super(out); + this.encryptingCipher = state.getEncryptingCipher(); + this.blockSize = encryptingCipher.getBlockSize(); + this.authTagLengthBits = state.getAuthTagLengthBits(); + out.write(state.getJweContentStart().getBytes("UTF-8")); + } + + @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(byte b[], int off, int len) throws IOException { + if (lastRawDataChunk != null) { + int remaining = blockSize - lastRawDataChunk.length; + int lenToCopy = remaining < len ? remaining : len; + lastRawDataChunk = newArray(lastRawDataChunk, 0, lastRawDataChunk.length, b, off, lenToCopy); + off = off + lenToCopy; + len -= lenToCopy; + if (lastRawDataChunk.length < blockSize) { + return; + } else { + encryptAndWrite(lastRawDataChunk, 0, lastRawDataChunk.length); + lastRawDataChunk = null; + } + } + int offset = 0; + for (; offset + blockSize <= len; offset += blockSize, off += blockSize) { + encryptAndWrite(b, off, blockSize); + } + if (offset < len) { + lastRawDataChunk = newArray(b, off, len - offset); + } + + } + + private void encryptAndWrite(byte[] chunk, int off, int len) throws IOException { + byte[] encrypted = encryptingCipher.update(chunk, off, len); + encodeAndWrite(encrypted, 0, encrypted.length, false); + } + private void encodeAndWrite(byte[] encryptedChunk, int off, int len, boolean finalWrite) throws IOException { + byte[] theChunk = lastEncryptedDataChunk; + int lenToEncode = len; + if (theChunk != null) { + theChunk = newArray(theChunk, 0, theChunk.length, encryptedChunk, off, len); + lenToEncode = theChunk.length; + off = 0; + } else { + theChunk = encryptedChunk; + } + int rem = finalWrite ? 0 : lenToEncode % 3; + encodeAndWriteFinally(theChunk, off, lenToEncode - rem); + + if (rem > 0) { + lastEncryptedDataChunk = newArray(theChunk, lenToEncode - rem, rem); + } else { + lastEncryptedDataChunk = null; + } + } + private void encodeAndWriteFinally(byte[] chunk, int off, int len) throws IOException { + String encoded = Base64UrlUtility.encodeChunk(chunk, off, len); + byte[] encodedBytes = encoded.getBytes("UTF-8"); + out.write(encodedBytes, 0, encodedBytes.length); + } + + @Override + public void flush() throws IOException { + if (flushed) { + return; + } + try { + byte[] finalBytes = lastRawDataChunk == null + ? encryptingCipher.doFinal() + : encryptingCipher.doFinal(lastRawDataChunk, 0, lastRawDataChunk.length); + encodeAndWrite(finalBytes, 0, finalBytes.length - authTagLengthBits / 8, true); + out.write('.'); + encodeAndWrite(finalBytes, finalBytes.length - authTagLengthBits / 8, authTagLengthBits / 8, true); + } catch (Exception ex) { + throw new SecurityException(); + } + flushed = true; + } + private byte[] newArray(byte[] src, int srcPos, int srcLen) { + byte[] buf = new byte[srcLen]; + System.arraycopy(src, srcPos, buf, 0, srcLen); + return buf; + } + private byte[] newArray(byte[] src, int srcPos, int srcLen, byte[] src2, int srcPos2, int srcLen2) { + byte[] buf = new byte[srcLen + srcLen2]; + System.arraycopy(src, srcPos, buf, 0, srcLen); + System.arraycopy(src2, srcPos2, buf, srcLen, srcLen2); + return buf; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/WrappedKeyJweEncryptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/WrappedKeyJweEncryptor.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/WrappedKeyJweEncryptor.java index 6f9154a..998be77 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/WrappedKeyJweEncryptor.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwe/WrappedKeyJweEncryptor.java @@ -19,6 +19,7 @@ package org.apache.cxf.rs.security.oauth2.jwe; import java.security.Key; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.cxf.rs.security.oauth2.jwt.Algorithm; import org.apache.cxf.rs.security.oauth2.jwt.JwtHeadersWriter; @@ -28,6 +29,7 @@ import org.apache.cxf.rs.security.oauth2.utils.crypto.KeyProperties; public class WrappedKeyJweEncryptor extends AbstractJweEncryptor { private Key cekEncryptionKey; private boolean wrap; + private AtomicInteger providedCekUsageCount; public WrappedKeyJweEncryptor(JweHeaders headers, Key cekEncryptionKey) { this(headers, cekEncryptionKey, null, null); } @@ -44,6 +46,9 @@ public class WrappedKeyJweEncryptor extends AbstractJweEncryptor { super(headers, cek, iv, authTagLen, writer); this.cekEncryptionKey = cekEncryptionKey; this.wrap = wrap; + if (cek != null) { + providedCekUsageCount = new AtomicInteger(); + } } protected byte[] getContentEncryptionKey() { byte[] theCek = super.getContentEncryptionKey(); @@ -52,6 +57,8 @@ public class WrappedKeyJweEncryptor extends AbstractJweEncryptor { String algoJwt = getContentEncryptionAlgoJwt(); theCek = CryptoUtils.getSecretKey(Algorithm.stripAlgoProperties(algoJava), Algorithm.valueOf(algoJwt).getKeySizeBits()).getEncoded(); + } else if (providedCekUsageCount.addAndGet(1) > 1) { + throw new SecurityException(); } return theCek; } http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwt/jaxrs/JweWriterInterceptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwt/jaxrs/JweWriterInterceptor.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwt/jaxrs/JweWriterInterceptor.java index 65dbb5f..8459052 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwt/jaxrs/JweWriterInterceptor.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/main/java/org/apache/cxf/rs/security/oauth2/jwt/jaxrs/JweWriterInterceptor.java @@ -37,7 +37,9 @@ import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.message.Message; import org.apache.cxf.rs.security.oauth2.jwe.JweEncryptor; +import org.apache.cxf.rs.security.oauth2.jwe.JweEncryptorWorkerState; import org.apache.cxf.rs.security.oauth2.jwe.JweHeaders; +import org.apache.cxf.rs.security.oauth2.jwe.JweOutputStream; import org.apache.cxf.rs.security.oauth2.jwe.WrappedKeyJweEncryptor; import org.apache.cxf.rs.security.oauth2.jwt.Algorithm; import org.apache.cxf.rs.security.oauth2.utils.crypto.CryptoUtils; @@ -49,15 +51,13 @@ public class JweWriterInterceptor implements WriterInterceptor { private static final String JSON_WEB_ENCRYPTION_ZIP_ALGO_PROP = "rs.security.jwe.zip.algorithm"; private JweEncryptor encryptor; private boolean contentTypeRequired = true; - + private boolean useJweOutputStream; @Override public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException, WebApplicationException { OutputStream actualOs = ctx.getOutputStream(); - CachedOutputStream cos = new CachedOutputStream(); - ctx.setOutputStream(cos); - ctx.proceed(); JweEncryptor theEncryptor = getInitializedEncryptor(); + String ctString = null; if (contentTypeRequired) { MediaType mt = ctx.getMediaType(); @@ -65,9 +65,22 @@ public class JweWriterInterceptor implements WriterInterceptor { ctString = JAXRSUtils.mediaTypeToString(mt); } } - String jweContent = theEncryptor.encrypt(cos.getBytes(), ctString); - IOUtils.copy(new ByteArrayInputStream(jweContent.getBytes("UTF-8")), actualOs); - actualOs.flush(); + + + if (useJweOutputStream) { + JweEncryptorWorkerState state = theEncryptor.newWorkerState(ctString); + JweOutputStream jweStream = new JweOutputStream(actualOs, state); + ctx.setOutputStream(jweStream); + ctx.proceed(); + jweStream.flush(); + } else { + CachedOutputStream cos = new CachedOutputStream(); + ctx.setOutputStream(cos); + ctx.proceed(); + String jweContent = theEncryptor.encrypt(cos.getBytes(), ctString); + IOUtils.copy(new ByteArrayInputStream(jweContent.getBytes("UTF-8")), actualOs); + actualOs.flush(); + } } protected JweEncryptor getInitializedEncryptor() { @@ -97,5 +110,9 @@ public class JweWriterInterceptor implements WriterInterceptor { throw new SecurityException(ex); } } + + public void setUseJweOutputStream(boolean useJweOutputStream) { + this.useJweOutputStream = useJweOutputStream; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2-jwt/src/test/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactReaderWriterTest.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2-jwt/src/test/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactReaderWriterTest.java b/rt/rs/security/oauth-parent/oauth2-jwt/src/test/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactReaderWriterTest.java index e2de7f6..2a80395 100644 --- a/rt/rs/security/oauth-parent/oauth2-jwt/src/test/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactReaderWriterTest.java +++ b/rt/rs/security/oauth-parent/oauth2-jwt/src/test/java/org/apache/cxf/rs/security/oauth2/jwe/JweCompactReaderWriterTest.java @@ -73,7 +73,7 @@ public class JweCompactReaderWriterTest extends Assert { public static void unregisterBouncyCastleIfNeeded() throws Exception { Security.removeProvider(BouncyCastleProvider.class.getName()); } - + @Test public void testEncryptDecryptSpecExample() throws Exception { final String specPlainText = "The true sign of intelligence is not knowledge but imagination."; @@ -110,11 +110,11 @@ public class JweCompactReaderWriterTest extends Assert { key, jwtKeyName, INIT_VECTOR); - return encryptor.encryptText(content, null); + return encryptor.encrypt(content.getBytes("UTF-8"), null); } private String encryptContentDirect(SecretKey key, String content) throws Exception { DirectKeyJweEncryptor encryptor = new DirectKeyJweEncryptor(key, INIT_VECTOR); - return encryptor.encryptText(content, null); + return encryptor.encrypt(content.getBytes("UTF-8"), null); } private void decrypt(String jweContent, String plainContent, boolean unwrap) throws Exception { RSAPrivateKey privateKey = CryptoUtils.getRSAPrivateKey(RSA_MODULUS_ENCODED, RSA_PRIVATE_EXPONENT_ENCODED); http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtils.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtils.java index 1d59467..99e46d8 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtils.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/crypto/CryptoUtils.java @@ -474,7 +474,7 @@ public final class CryptoUtils { new KeyProperties(wrapperKeyAlgo)); } - public static byte[] wrapSecretKey(SecretKey secretKey, + public static byte[] wrapSecretKey(Key secretKey, Key wrapperKey, KeyProperties keyProps) throws SecurityException { try { @@ -533,13 +533,15 @@ public final class CryptoUtils { } boolean updateRequired = keyProps != null && keyProps.getAdditionalData() != null; int offset = 0; - for (; offset + blockSize < bytes.length; offset += blockSize) { + for (; offset + blockSize <= bytes.length; offset += blockSize) { byte[] next = !updateRequired ? c.doFinal(bytes, offset, blockSize) : c.update(bytes, offset, blockSize); result = addToResult(result, next); } if (offset < bytes.length) { result = addToResult(result, c.doFinal(bytes, offset, bytes.length - offset)); + } else { + result = addToResult(result, c.doFinal()); } } if (compressionSupported && mode == Cipher.DECRYPT_MODE) { http://git-wip-us.apache.org/repos/asf/cxf/blob/27e80bbf/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 a86a3b2..15f40dd 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 @@ -82,7 +82,9 @@ public class JAXRSJweJwsTest extends AbstractBusClientServerTestBase { bean.setServiceClass(BookStore.class); bean.setAddress(address); List<Object> providers = new LinkedList<Object>(); - providers.add(new JweWriterInterceptor()); + JweWriterInterceptor jweWriter = new JweWriterInterceptor(); + jweWriter.setUseJweOutputStream(true); + providers.add(jweWriter); providers.add(new JweClientResponseFilter()); providers.add(new JwsWriterInterceptor()); providers.add(new JwsClientResponseFilter());
