8146514: Enforce GCM limits
authorvaleriep
Wed, 30 Mar 2016 22:02:23 +0000
changeset 39752 365d487907ac
parent 39751 47948cf5c3c5
child 39753 f3cf4a07b0f6
8146514: Enforce GCM limits Summary: add and enforce upper limit for input size for AES cipher in GCM mode Reviewed-by: mullan
jdk/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java
jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeGCMCipher.java
--- a/jdk/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Wed Mar 16 21:59:55 2016 +0000
+++ b/jdk/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java	Wed Mar 30 22:02:23 2016 +0000
@@ -49,6 +49,16 @@
     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 @@
         }
     }
 
-    // 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 @@
         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 @@
             } 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 @@
         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 @@
      */
     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 @@
         if ((len % blockSize) != 0) {
              throw new ProviderException("Internal error in input buffering");
         }
+
+        checkDataLength(ibuffer.size(), len);
+
         processAAD();
 
         if (len > 0) {
@@ -483,10 +525,21 @@
         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 @@
         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();
--- a/jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeGCMCipher.java	Wed Mar 16 21:59:55 2016 +0000
+++ b/jdk/src/jdk.crypto.ucrypto/solaris/classes/com/oracle/security/ucrypto/NativeGCMCipher.java	Wed Mar 30 22:02:23 2016 +0000
@@ -57,6 +57,9 @@
 
     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 @@
     // 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 @@
     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 @@
             ibuffer.reset();
         }
         if (!encrypt) requireReinit = false;
+        processed = 0;
     }
 
     // actual init() implementation - caller should clone key and iv if needed
@@ -261,6 +280,7 @@
             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 @@
             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 @@
             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);