8146514: Enforce GCM limits

Add and enforce upper limit for input size for AES cipher in GCM mode

Reviewed-by: mullan
This commit is contained in:
Valerie Peng 2016-03-30 22:02:23 +00:00
parent 0291dce4bf
commit f51ba0de72
2 changed files with 83 additions and 12 deletions

View File

@ -49,6 +49,16 @@ final class GaloisCounterMode extends FeedbackCipher {
static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE;
static int DEFAULT_IV_LEN = 12; // in bytes
// In NIST SP 800-38D, GCM input size is limited to be no longer
// than (2^36 - 32) bytes. Otherwise, the counter will wrap
// around and lead to a leak of plaintext.
// However, given the current GCM spec requirement that recovered
// text can only be returned after successful tag verification,
// we are bound by limiting the data size to the size limit of
// java byte array, e.g. Integer.MAX_VALUE, since all data
// can only be returned by the doFinal(...) call.
private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
// buffer for AAD data; if null, meaning update has been called
private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream();
private int sizeOfAAD = 0;
@ -89,9 +99,13 @@ final class GaloisCounterMode extends FeedbackCipher {
}
}
// ivLen in bits
private static byte[] getLengthBlock(int ivLen) {
private static byte[] getLengthBlock(int ivLenInBytes) {
long ivLen = ((long)ivLenInBytes) << 3;
byte[] out = new byte[AES_BLOCK_SIZE];
out[8] = (byte)(ivLen >>> 56);
out[9] = (byte)(ivLen >>> 48);
out[10] = (byte)(ivLen >>> 40);
out[11] = (byte)(ivLen >>> 32);
out[12] = (byte)(ivLen >>> 24);
out[13] = (byte)(ivLen >>> 16);
out[14] = (byte)(ivLen >>> 8);
@ -99,13 +113,22 @@ final class GaloisCounterMode extends FeedbackCipher {
return out;
}
// aLen and cLen both in bits
private static byte[] getLengthBlock(int aLen, int cLen) {
private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) {
long aLen = ((long)aLenInBytes) << 3;
long cLen = ((long)cLenInBytes) << 3;
byte[] out = new byte[AES_BLOCK_SIZE];
out[0] = (byte)(aLen >>> 56);
out[1] = (byte)(aLen >>> 48);
out[2] = (byte)(aLen >>> 40);
out[3] = (byte)(aLen >>> 32);
out[4] = (byte)(aLen >>> 24);
out[5] = (byte)(aLen >>> 16);
out[6] = (byte)(aLen >>> 8);
out[7] = (byte)aLen;
out[8] = (byte)(cLen >>> 56);
out[9] = (byte)(cLen >>> 48);
out[10] = (byte)(cLen >>> 40);
out[11] = (byte)(cLen >>> 32);
out[12] = (byte)(cLen >>> 24);
out[13] = (byte)(cLen >>> 16);
out[14] = (byte)(cLen >>> 8);
@ -142,13 +165,20 @@ final class GaloisCounterMode extends FeedbackCipher {
} else {
g.update(iv);
}
byte[] lengthBlock = getLengthBlock(iv.length*8);
byte[] lengthBlock = getLengthBlock(iv.length);
g.update(lengthBlock);
j0 = g.digest();
}
return j0;
}
private static void checkDataLength(int processed, int len) {
if (processed > MAX_BUF_SIZE - len) {
throw new ProviderException("SunJCE provider only supports " +
"input size up to " + MAX_BUF_SIZE + " bytes");
}
}
GaloisCounterMode(SymmetricCipher embeddedCipher) {
super(embeddedCipher);
aadBuffer = new ByteArrayOutputStream();
@ -386,6 +416,9 @@ final class GaloisCounterMode extends FeedbackCipher {
if ((len % blockSize) != 0) {
throw new ProviderException("Internal error in input buffering");
}
checkDataLength(processed, len);
processAAD();
if (len > 0) {
gctrPAndC.update(in, inOfs, len, out, outOfs);
@ -407,17 +440,23 @@ final class GaloisCounterMode extends FeedbackCipher {
*/
int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
throws IllegalBlockSizeException, ShortBufferException {
if (len > MAX_BUF_SIZE - tagLenBytes) {
throw new ShortBufferException
("Can't fit both data and tag into one buffer");
}
if (out.length - outOfs < (len + tagLenBytes)) {
throw new ShortBufferException("Output buffer too small");
}
checkDataLength(processed, len);
processAAD();
if (len > 0) {
doLastBlock(in, inOfs, len, out, outOfs, true);
}
byte[] lengthBlock =
getLengthBlock(sizeOfAAD*8, processed*8);
getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(lengthBlock);
byte[] s = ghashAllToS.digest();
byte[] sOut = new byte[s.length];
@ -449,6 +488,9 @@ final class GaloisCounterMode extends FeedbackCipher {
if ((len % blockSize) != 0) {
throw new ProviderException("Internal error in input buffering");
}
checkDataLength(ibuffer.size(), len);
processAAD();
if (len > 0) {
@ -483,10 +525,21 @@ final class GaloisCounterMode extends FeedbackCipher {
if (len < tagLenBytes) {
throw new AEADBadTagException("Input too short - need tag");
}
// do this check here can also catch the potential integer overflow
// scenario for the subsequent output buffer capacity check.
checkDataLength(ibuffer.size(), (len - tagLenBytes));
if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) {
throw new ShortBufferException("Output buffer too small");
}
processAAD();
// get the trailing tag bytes from 'in'
byte[] tag = new byte[tagLenBytes];
System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes);
len -= tagLenBytes;
if (len != 0) {
ibuffer.write(in, inOfs, len);
}
@ -497,17 +550,12 @@ final class GaloisCounterMode extends FeedbackCipher {
len = in.length;
ibuffer.reset();
byte[] tag = new byte[tagLenBytes];
// get the trailing tag bytes from 'in'
System.arraycopy(in, len - tagLenBytes, tag, 0, tagLenBytes);
len -= tagLenBytes;
if (len > 0) {
doLastBlock(in, inOfs, len, out, outOfs, false);
}
byte[] lengthBlock =
getLengthBlock(sizeOfAAD*8, processed*8);
getLengthBlock(sizeOfAAD, processed);
ghashAllToS.update(lengthBlock);
byte[] s = ghashAllToS.digest();

View File

@ -57,6 +57,9 @@ class NativeGCMCipher extends NativeCipher {
private static final int DEFAULT_TAG_LEN = 128; // same as SunJCE provider
// same as SunJCE provider, see GaloisCounterMode.java for details
private static final int MAX_BUF_SIZE = Integer.MAX_VALUE;
// buffer for storing AAD data; if null, meaning buffer content has been
// supplied to native context
private ByteArrayOutputStream aadBuffer;
@ -64,6 +67,9 @@ class NativeGCMCipher extends NativeCipher {
// buffer for storing input in decryption, not used for encryption
private ByteArrayOutputStream ibuffer;
// needed for checking against MAX_BUF_SIZE
private int processed;
private int tagLen = DEFAULT_TAG_LEN;
/*
@ -79,6 +85,18 @@ class NativeGCMCipher extends NativeCipher {
private byte[] lastEncKey = null;
private byte[] lastEncIv = null;
private void checkAndUpdateProcessed(int len) {
// Currently, cipher text and tag are packed in one byte array, so
// the impl-specific limit for input data size is (MAX_BUF_SIZE - tagLen)
int inputDataLimit = MAX_BUF_SIZE - tagLen;
if (processed > inputDataLimit - len) {
throw new ProviderException("OracleUcrypto provider only supports " +
"input size up to " + inputDataLimit + " bytes");
}
processed += len;
}
NativeGCMCipher(int fixedKeySize) throws NoSuchAlgorithmException {
super(UcryptoMech.CRYPTO_AES_GCM, fixedKeySize);
}
@ -138,6 +156,7 @@ class NativeGCMCipher extends NativeCipher {
ibuffer.reset();
}
if (!encrypt) requireReinit = false;
processed = 0;
}
// actual init() implementation - caller should clone key and iv if needed
@ -261,6 +280,7 @@ class NativeGCMCipher extends NativeCipher {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (inLen > 0) {
if (!encrypt) {
ibuffer.write(in, inOfs, inLen);
@ -291,6 +311,7 @@ class NativeGCMCipher extends NativeCipher {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (inLen > 0) {
if (!encrypt) {
ibuffer.write(in, inOfs, inLen);
@ -393,6 +414,8 @@ class NativeGCMCipher extends NativeCipher {
throw new IllegalStateException
("Must use either different key or iv for GCM encryption");
}
checkAndUpdateProcessed(inLen);
if (!encrypt) {
if (inLen > 0) {
ibuffer.write(in, inOfs, inLen);