8318756: Create better internal buffer for AEADs

Reviewed-by: djelinski
This commit is contained in:
Anthony Scarpino 2023-12-06 18:09:10 +00:00
parent a9cb120d03
commit dc9c77bebe
14 changed files with 1178 additions and 450 deletions

View file

@ -25,7 +25,6 @@
package com.sun.crypto.provider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@ -64,7 +63,6 @@ abstract class ChaCha20Cipher extends CipherSpi {
private static final int KS_MAX_LEN = 1024;
private static final int KS_BLK_SIZE = 64;
private static final int KS_SIZE_INTS = KS_BLK_SIZE / Integer.BYTES;
private static final int CIPHERBUF_BASE = 1024;
// The initialization state of the cipher
private boolean initialized;
@ -650,7 +648,7 @@ abstract class ChaCha20Cipher extends CipherSpi {
try {
engine.doUpdate(in, inOfs, inLen, out, 0);
} catch (ShortBufferException | KeyException exc) {
throw new RuntimeException(exc);
throw new ProviderException(exc);
}
return out;
@ -681,11 +679,35 @@ abstract class ChaCha20Cipher extends CipherSpi {
try {
bytesUpdated = engine.doUpdate(in, inOfs, inLen, out, outOfs);
} catch (KeyException ke) {
throw new RuntimeException(ke);
throw new ProviderException(ke);
}
return bytesUpdated;
}
/**
* Update the currently running operation with additional data
*
* @param input the plaintext or ciphertext ByteBuffer
* @param output ByteBuffer that will hold the resulting data. This
* must be large enough to hold the resulting data.
*
* @return the length in bytes of the data written into the {@code out}
* buffer.
*
* @throws ShortBufferException if the buffer {@code out} does not have
* enough space to hold the resulting data.
*/
@Override
protected int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
try {
return bufferCrypt(input, output, true);
} catch (AEADBadTagException e) {
// exception is never thrown by update ops
throw new AssertionError(e);
}
}
/**
* Complete the currently running operation using any final
* data provided by the caller.
@ -753,6 +775,118 @@ abstract class ChaCha20Cipher extends CipherSpi {
return bytesUpdated;
}
/**
* Complete the currently running operation using any final
* data provided by the caller.
*
* @param input the plaintext or ciphertext input bytebuffer.
* @param output ByteBuffer that will hold the resulting data. This
* must be large enough to hold the resulting data.
*
* @return the resulting plaintext or ciphertext bytes.
*
* @throws AEADBadTagException if, during decryption, the provided tag
* does not match the calculated tag.
*/
@Override
protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, AEADBadTagException {
return bufferCrypt(input, output, false);
}
/*
* Optimized version of bufferCrypt from CipherSpi.java. Direct
* ByteBuffers send to the engine code.
*/
private int bufferCrypt(ByteBuffer input, ByteBuffer output,
boolean isUpdate) throws ShortBufferException, AEADBadTagException {
if ((input == null) || (output == null)) {
throw new NullPointerException
("Input and output buffers must not be null");
}
int inPos = input.position();
int inLimit = input.limit();
int inLen = inLimit - inPos;
if (isUpdate && (inLen == 0)) {
return 0;
}
int outLenNeeded = engine.getOutputSize(inLen, !isUpdate);
if (output.remaining() < outLenNeeded) {
throw new ShortBufferException("Need at least " + outLenNeeded
+ " bytes of space in output buffer");
}
int total = 0;
// Check if input bytebuffer is heap-backed
if (input.hasArray()) {
byte[] inArray = input.array();
int inOfs = input.arrayOffset() + inPos;
byte[] outArray;
// Check if output bytebuffer is heap-backed
if (output.hasArray()) {
outArray = output.array();
int outPos = output.position();
int outOfs = output.arrayOffset() + outPos;
// check array address and offsets and use temp output buffer
// if output offset is larger than input offset and
// falls within the range of input data
boolean useTempOut = false;
if (inArray == outArray &&
((inOfs < outOfs) && (outOfs < inOfs + inLen))) {
useTempOut = true;
outArray = new byte[outLenNeeded];
outOfs = 0;
}
try {
if (isUpdate) {
total = engine.doUpdate(inArray, inOfs, inLen, outArray,
outOfs);
} else {
total = engine.doFinal(inArray, inOfs, inLen, outArray,
outOfs);
}
} catch (KeyException e) {
throw new ProviderException(e);
}
if (useTempOut) {
output.put(outArray, outOfs, total);
} else {
// adjust output position manually
output.position(outPos + total);
}
} else { // if output is direct
if (isUpdate) {
outArray = engineUpdate(inArray, inOfs, inLen);
} else {
outArray = engineDoFinal(inArray, inOfs, inLen);
}
if (outArray != null && outArray.length != 0) {
output.put(outArray);
total = outArray.length;
}
}
// adjust input position manually
input.position(inLimit);
} else { // Bytebuffers are both direct
try {
if (isUpdate) {
return engine.doUpdate(input, output);
}
return engine.doFinal(input, output);
} catch (KeyException e) {
throw new ProviderException(e);
}
}
return total;
}
/**
* Wrap a {@code Key} using this Cipher's current encryption parameters.
*
@ -1243,6 +1377,11 @@ abstract class ChaCha20Cipher extends CipherSpi {
*/
int doFinal(byte[] in, int inOff, int inLen, byte[] out, int outOff)
throws ShortBufferException, AEADBadTagException, KeyException;
int doUpdate(ByteBuffer input, ByteBuffer output) throws
ShortBufferException, KeyException;
int doFinal(ByteBuffer input, ByteBuffer output) throws
ShortBufferException, KeyException, AEADBadTagException;
}
private final class EngineStreamOnly implements ChaChaEngine {
@ -1285,6 +1424,22 @@ abstract class ChaCha20Cipher extends CipherSpi {
int outOff) throws ShortBufferException, KeyException {
return doUpdate(in, inOff, inLen, out, outOff);
}
@Override
public int doUpdate(ByteBuffer input, ByteBuffer output) throws
ShortBufferException, KeyException {
byte[] data = new byte[input.remaining()];
input.get(data);
doUpdate(data, 0, data.length, data, 0);
output.put(data);
return data.length;
}
@Override
public int doFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, KeyException {
return doUpdate(input, output);
}
}
private final class EngineAEADEnc implements ChaChaEngine {
@ -1348,11 +1503,32 @@ abstract class ChaCha20Cipher extends CipherSpi {
aadDone = false;
return inLen + TAG_LENGTH;
}
@Override
public int doUpdate(ByteBuffer input, ByteBuffer output) throws
ShortBufferException, KeyException {
byte[] data = new byte[input.remaining()];
input.get(data);
doUpdate(data, 0, data.length, data, 0);
output.put(data);
return data.length;
}
@Override
public int doFinal(ByteBuffer input, ByteBuffer output) throws
ShortBufferException, KeyException {
int len = input.remaining();
byte[] data = new byte[len + TAG_LENGTH];
input.get(data, 0, len);
doFinal(data, 0, len, data, 0);
output.put(data);
return data.length;
}
}
private final class EngineAEADDec implements ChaChaEngine {
private final ByteArrayOutputStream cipherBuf;
private AEADBufferedStream cipherBuf = null;
private final byte[] tag;
@Override
@ -1364,20 +1540,32 @@ abstract class ChaCha20Cipher extends CipherSpi {
// size.
return (isFinal ?
Integer.max(Math.addExact((inLen - TAG_LENGTH),
cipherBuf.size()), 0) : 0);
getBufferedLength()), 0) : 0);
}
private void initBuffer(int len) {
if (cipherBuf == null) {
cipherBuf = new AEADBufferedStream(len);
}
}
private int getBufferedLength() {
if (cipherBuf != null) {
return cipherBuf.size();
}
return 0;
}
private EngineAEADDec() throws InvalidKeyException {
initAuthenticator();
initCounterValue = 1;
counter = initCounterValue;
cipherBuf = new ByteArrayOutputStream(CIPHERBUF_BASE);
tag = new byte[TAG_LENGTH];
}
@Override
public int doUpdate(byte[] in, int inOff, int inLen, byte[] out,
int outOff) {
int outOff) {
if (initialized) {
// If this is the first update since AAD updates, signal that
// we're done processing AAD info and pad the AAD to a multiple
@ -1389,6 +1577,7 @@ abstract class ChaCha20Cipher extends CipherSpi {
if (in != null) {
Objects.checkFromIndexSize(inOff, inLen, in.length);
initBuffer(inLen);
cipherBuf.write(in, inOff, inLen);
}
} else {
@ -1399,6 +1588,14 @@ abstract class ChaCha20Cipher extends CipherSpi {
return 0;
}
@Override
public int doUpdate(ByteBuffer input, ByteBuffer output) {
initBuffer(input.remaining());
cipherBuf.write(input);
return 0;
}
@Override
public int doFinal(byte[] in, int inOff, int inLen, byte[] out,
int outOff) throws ShortBufferException, AEADBadTagException,
@ -1406,7 +1603,7 @@ abstract class ChaCha20Cipher extends CipherSpi {
byte[] ctPlusTag;
int ctPlusTagLen;
if (cipherBuf.size() == 0 && inOff == 0) {
if (getBufferedLength() == 0) {
// No previous data has been seen before doFinal, so we do
// not need to hold any ciphertext in a buffer. We can
// process it directly from the "in" parameter.
@ -1415,10 +1612,11 @@ abstract class ChaCha20Cipher extends CipherSpi {
ctPlusTagLen = inLen;
} else {
doUpdate(in, inOff, inLen, out, outOff);
ctPlusTag = cipherBuf.toByteArray();
ctPlusTagLen = ctPlusTag.length;
ctPlusTag = cipherBuf.getBuffer();
inOff = 0;
ctPlusTagLen = cipherBuf.size();
cipherBuf.reset();
}
cipherBuf.reset();
// There must at least be a tag length's worth of ciphertext
// data in the buffered input.
@ -1436,19 +1634,80 @@ abstract class ChaCha20Cipher extends CipherSpi {
// Calculate and compare the tag. Only do the decryption
// if and only if the tag matches.
authFinalizeData(ctPlusTag, 0, ctLen, tag, 0);
long tagCompare = ((long)asLongView.get(ctPlusTag, ctLen) ^
authFinalizeData(ctPlusTag, inOff, ctLen, tag, 0);
long tagCompare = ((long)asLongView.get(ctPlusTag, ctLen + inOff) ^
(long)asLongView.get(tag, 0)) |
((long)asLongView.get(ctPlusTag, ctLen + Long.BYTES) ^
((long)asLongView.get(ctPlusTag, ctLen + inOff + Long.BYTES) ^
(long)asLongView.get(tag, Long.BYTES));
if (tagCompare != 0) {
throw new AEADBadTagException("Tag mismatch");
}
chaCha20Transform(ctPlusTag, 0, ctLen, out, outOff);
chaCha20Transform(ctPlusTag, inOff, ctLen, out, outOff);
aadDone = false;
return ctLen;
}
@Override
public int doFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, AEADBadTagException, KeyException {
int len;
int inLen = input.remaining();
byte[] ct = null, buf = null;
//buf = (getBufferedLength() == 0 ? null : cipherBuf.toByteArray());
int bufLen = 0;
// The length of cipher text and tag
int ctLen = getBufferedLength() + inLen;
if (ctLen < TAG_LENGTH) {
throw new AEADBadTagException("Input too short - need tag");
}
if (inLen < TAG_LENGTH) {
if (inLen > 0) {
doUpdate(input, output);
}
if (cipherBuf != null) {
ct = cipherBuf.getBuffer();
}
len = ctLen;
} else {
if (cipherBuf != null) {
buf = cipherBuf.getBuffer();
bufLen = cipherBuf.size();
}
ct = new byte[inLen];
input.get(ct, 0, inLen);
len = inLen;
}
doUpdate(null, 0, 0, null, 0);
// If there is an internal buffer, calculate its tag contribution.
if (buf != null) {
dataLen = authUpdate(buf, 0, bufLen);
}
// Complete tag calculation
len -= TAG_LENGTH;
authFinalizeData(ct, 0, len, tag, 0);
// Check tag
if ((((long) asLongView.get(ct, len) ^
(long) asLongView.get(tag, 0)) |
((long) asLongView.get(ct, len + Long.BYTES) ^
(long) asLongView.get(tag, Long.BYTES))) != 0) {
throw new AEADBadTagException("Tag mismatch");
}
// decrypt internal buffer in-place, then put it into the bytebuffer
if (buf != null) {
chaCha20Transform(buf, 0, bufLen, buf, 0);
output.put(buf, 0, bufLen);
}
// decrypt input buffer in-place, append it to the bytebuffer
chaCha20Transform(ct, 0, len, ct, 0);
output.put(ct, 0, len);
aadDone = false;
return ctLen - TAG_LENGTH;
}
}
public static final class ChaCha20Only extends ChaCha20Cipher {