Skip to content

Fixes for AEAD output size calculations and use in cipher streams #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.bouncycastle.crypto.io;

import java.io.IOException;

/**
* {@link IOException} wrapper around an exception indicating a problem with the use of a cipher.
*/
public class CipherIOException
extends IOException
{
private static final long serialVersionUID = 1L;

private final Throwable cause;

public CipherIOException(String message, Throwable cause)
{
super(message);

this.cause = cause;
}

public Throwable getCause()
{
return cause;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@
/**
* 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.
Expand All @@ -45,11 +45,6 @@ public 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(
Expand All @@ -59,9 +54,6 @@ public CipherInputStream(
super(is);

this.streamCipher = cipher;

buf = new byte[INPUT_BUF_SIZE];
inBuf = new byte[INPUT_BUF_SIZE];
}

/**
Expand All @@ -72,11 +64,6 @@ public CipherInputStream(InputStream is, AEADBlockCipher cipher)
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];
}

/**
Expand Down Expand Up @@ -112,6 +99,7 @@ private int nextChunk()

try
{
ensureCapacity(read, false);
if (bufferedBlockCipher != null)
{
maxBuf = bufferedBlockCipher.processBytes(inBuf, 0, read, buf, 0);
Expand All @@ -128,7 +116,7 @@ else if (aeadBlockCipher != null)
}
catch (Exception e)
{
throw new IOException("Error processing stream " + e);
throw new CipherIOException("Error processing stream ", e);
}
}
return maxBuf;
Expand All @@ -140,6 +128,7 @@ private void finaliseCipher()
try
{
finalized = true;
ensureCapacity(0, true);
if (bufferedBlockCipher != null)
{
maxBuf = bufferedBlockCipher.doFinal(buf, 0);
Expand All @@ -159,7 +148,7 @@ else if (aeadBlockCipher != null)
}
catch (Exception e)
{
throw new IOException("Error finalising cipher " + e);
throw new CipherIOException("Error finalising cipher ", e);
}
}

Expand Down Expand Up @@ -262,12 +251,50 @@ public int available()
return maxBuf - bufOff;
}

/**
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void write(
int len)
throws IOException
{
ensureCapacity(len);
ensureCapacity(len, false);

if (bufferedBlockCipher != null)
{
Expand Down Expand Up @@ -149,24 +149,35 @@ else if (aeadBlockCipher != null)
/**
* Ensure the ciphertext buffer has space sufficient to accept an upcoming output.
*
* @param outputSize the size of the pending update.
* @param updateSize the size of the pending update.
* @param <code>true</code> iff this the cipher is to be finalised.
*/
private void ensureCapacity(int outputSize)
private void ensureCapacity(int updateSize, boolean finalOutput)
{
// This overestimates buffer on updates for AEAD/padded, but keeps it simple.
int bufLen;
if (bufferedBlockCipher != null)
int bufLen = updateSize;
if (finalOutput)
{
bufLen = bufferedBlockCipher.getOutputSize(outputSize);
}
else if (aeadBlockCipher != null)
{
bufLen = aeadBlockCipher.getOutputSize(outputSize);
if (bufferedBlockCipher != null)
{
bufLen = bufferedBlockCipher.getOutputSize(updateSize);
}
else if (aeadBlockCipher != null)
{
bufLen = aeadBlockCipher.getOutputSize(updateSize);
}
}
else
{
bufLen = outputSize;
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];
Expand Down Expand Up @@ -213,7 +224,7 @@ public void flush()
public void close()
throws IOException
{
ensureCapacity(0);
ensureCapacity(0, true);
IOException error = null;
try
{
Expand Down Expand Up @@ -242,7 +253,7 @@ else if (aeadBlockCipher != null)
}
catch (Exception e)
{
error = new IOException("Error closing stream: " + e);
error = new CipherIOException("Error closing stream: ", e);
}

try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,12 @@
* expose invalid ciphertext errors.
*/
public class InvalidCipherTextIOException
extends IOException
extends CipherIOException
{
private static final long serialVersionUID = 1L;

private final Throwable cause;

public InvalidCipherTextIOException(String message, Throwable cause)
{
super(message);

this.cause = cause;
}

public Throwable getCause()
{
return cause;
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@

/**
* A block cipher mode that includes authenticated encryption with a streaming mode and optional associated data.
* <p/>
* Implementations of this interface may operate in a packet mode (where all input data is buffered and
* processed dugin the call to {@link #doFinal(byte[], int)}), or in a streaming mode (where output data is
* incrementally produced with each call to {@link #processByte(byte, byte[], int)} or
* {@link #processBytes(byte[], int, int, byte[], int)}.
* <br/>This is important to consider during decryption: in a streaming mode, unauthenticated plaintext data
* may be output prior to the call to {@link #doFinal(byte[], int)} that results in an authentication
* failure. The higher level protocol utilising this cipher must ensure the plaintext data is handled
* appropriately until the end of data is reached and the entire ciphertext is authenticated.
* @see org.bouncycastle.crypto.params.AEADParameters
*/
public interface AEADBlockCipher
Expand Down Expand Up @@ -101,7 +110,11 @@ public int doFinal(byte[] out, int outOff)
/**
* return the size of the output buffer required for a processBytes
* an input of len bytes.
*
* <p/>
* The returned size may be dependent on the initialisation of this cipher
* and may not be accurate once subsequent input data is processed - this method
* should be invoked immediately prior to input data being processed.
*
* @param len the length of the input.
* @return the space required to accommodate a call to processBytes
* with len bytes of input.
Expand All @@ -111,7 +124,12 @@ public int doFinal(byte[] out, int outOff)
/**
* return the size of the output buffer required for a processBytes plus a
* doFinal with an input of len bytes.
*
* <p/>
* The returned size may be dependent on the initialisation of this cipher
* and may not be accurate once subsequent input data is processed - this method
* should be invoked immediately prior to a call to final processing of input data
* and a call to {@link #doFinal(byte[], int)}.
*
* @param len the length of the input.
* @return the space required to accommodate a call to processBytes and doFinal
* with len bytes of input.
Expand Down
Loading