diff options
author | Tim Whittington <bc@whittington.net.nz> | 2014-03-07 00:15:11 +0400 |
---|---|---|
committer | Tim Whittington <bc@whittington.net.nz> | 2014-03-10 12:27:39 +0400 |
commit | f5212359caf7e5931ae0f48f00e328cc3b017317 (patch) | |
tree | 59c9fdb29509651205f28019b1d9c187de515955 /core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java | |
parent | 8eca220a9b2c68938b19ee6b88e0534d0a07c618 (diff) |
Fix buffer underflows in cipher light weight API input/output streams and beef up testing.
Buffer underflows could occur when:
- decrypting data > internal buffer size in output stream (input stream was fixed in prior commit)
- packet mode AE cipher (e.g. CCM) is used with a data size > internal buffer size (since all output is buffered)
Buffer is now sized appropriately to every cipher operation immediately prior to it (using getUpdateOutputSize/getOutputSize as appropriate) in both streams.
Tests now run over boundaries of various block/buffer sizes to try to expose issues (0, 64 bit block, 128 bit block, 1K, 2K, 4K).
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java')
-rw-r--r-- | core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java | 71 |
1 files changed, 49 insertions, 22 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java b/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java index 66772df5..f80add1a 100644 --- a/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java +++ b/core/src/main/java/org/bouncycastle/crypto/io/CipherInputStream.java @@ -12,28 +12,28 @@ import org.bouncycastle.crypto.modes.AEADBlockCipher; /** * A CipherInputStream is composed of an InputStream and a cipher so that read() methods return data * that are read in from the underlying InputStream but have been additionally processed by the - * Cipher. The cipher must be fully initialized before being used by a CipherInputStream. + * cipher. The cipher must be fully initialized before being used by a CipherInputStream. * <p/> - * For example, if the Cipher is initialized for decryption, the + * For example, if the cipher is initialized for decryption, the * CipherInputStream will attempt to read in data and decrypt them, * before returning the decrypted data. */ public class CipherInputStream extends FilterInputStream { + private static final int INPUT_BUF_SIZE = 2048; + private BufferedBlockCipher bufferedBlockCipher; private StreamCipher streamCipher; private AEADBlockCipher aeadBlockCipher; - private final byte[] buf; - private final byte[] inBuf; + private byte[] buf; + private final byte[] inBuf = new byte[INPUT_BUF_SIZE]; private int bufOff; private int maxBuf; private boolean finalized; - private static final int INPUT_BUF_SIZE = 2048; - /** * Constructs a CipherInputStream from an InputStream and a * BufferedBlockCipher. @@ -45,11 +45,6 @@ public class CipherInputStream super(is); this.bufferedBlockCipher = cipher; - - int outSize = cipher.getOutputSize(INPUT_BUF_SIZE); - - buf = new byte[(outSize > INPUT_BUF_SIZE) ? outSize : INPUT_BUF_SIZE]; - inBuf = new byte[INPUT_BUF_SIZE]; } public CipherInputStream( @@ -59,9 +54,6 @@ public class CipherInputStream super(is); this.streamCipher = cipher; - - buf = new byte[INPUT_BUF_SIZE]; - inBuf = new byte[INPUT_BUF_SIZE]; } /** @@ -72,11 +64,6 @@ public class CipherInputStream super(is); this.aeadBlockCipher = cipher; - - int outSize = cipher.getOutputSize(INPUT_BUF_SIZE); - - buf = new byte[(outSize > INPUT_BUF_SIZE) ? outSize : INPUT_BUF_SIZE]; - inBuf = new byte[INPUT_BUF_SIZE]; } /** @@ -112,6 +99,7 @@ public class CipherInputStream try { + ensureCapacity(read, false); if (bufferedBlockCipher != null) { maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, read, buf, 0); @@ -128,7 +116,7 @@ public class CipherInputStream } catch (Exception e) { - throw new IOException("Error processing stream " + e); + throw new CipherIOException("Error processing stream ", e); } } return maxBuf; @@ -140,6 +128,7 @@ public class CipherInputStream try { finalized = true; + ensureCapacity(0, true); if (bufferedBlockCipher != null) { maxBuf = bufferedBlockCipher.doFinal(buf, 0); @@ -159,7 +148,7 @@ public class CipherInputStream } catch (Exception e) { - throw new IOException("Error finalising cipher " + e); + throw new CipherIOException("Error finalising cipher ", e); } } @@ -263,11 +252,49 @@ public class CipherInputStream } /** + * Ensure the ciphertext buffer has space sufficient to accept an upcoming output. + * + * @param updateSize the size of the pending update. + * @param <code>true</code> iff this the cipher is to be finalised. + */ + private void ensureCapacity(int updateSize, boolean finalOutput) + { + int bufLen = updateSize; + if (finalOutput) + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getOutputSize(updateSize); + } + } + else + { + if (bufferedBlockCipher != null) + { + bufLen = bufferedBlockCipher.getUpdateOutputSize(updateSize); + } + else if (aeadBlockCipher != null) + { + bufLen = aeadBlockCipher.getUpdateOutputSize(updateSize); + } + } + + if ((buf == null) || (buf.length < bufLen)) + { + buf = new byte[bufLen]; + } + } + + /** * Closes the underlying input stream and finalises the processing of the data by the cipher. * * @throws IOException if there was an error closing the input stream. * @throws InvalidCipherTextIOException if the data read from the stream was invalid ciphertext - * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). + * (e.g. the cipher is an AEAD cipher and the ciphertext tag check fails). */ public void close() throws IOException |