8158589: Possible integer overflow issues for DRBG
authorweijun
Wed, 06 Jul 2016 21:52:12 +0800
changeset 39481 63ceb7ef04d4
parent 39480 50a182a81875
child 39482 11b54f3b1ba7
8158589: Possible integer overflow issues for DRBG Reviewed-by: xuelei, mullan
jdk/src/java.base/share/classes/sun/security/provider/AbstractDrbg.java
jdk/src/java.base/share/classes/sun/security/provider/AbstractHashDrbg.java
jdk/src/java.base/share/classes/sun/security/provider/CtrDrbg.java
jdk/src/java.base/share/classes/sun/security/provider/HashDrbg.java
jdk/src/java.base/share/classes/sun/security/provider/HmacDrbg.java
--- a/jdk/src/java.base/share/classes/sun/security/provider/AbstractDrbg.java	Wed Jul 06 01:20:20 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/AbstractDrbg.java	Wed Jul 06 21:52:12 2016 +0800
@@ -377,11 +377,12 @@
 
             instantiateIfNecessary(null);
 
-            // Step 7: Auto reseed
+            // Step 7: Auto reseed (reseedCounter might overflow)
             // Double checked locking, safe because reseedCounter is volatile
-            if (reseedCounter > reseedInterval || pr) {
+            if (reseedCounter < 0 || reseedCounter > reseedInterval || pr) {
                 synchronized (this) {
-                    if (reseedCounter > reseedInterval || pr) {
+                    if (reseedCounter < 0 || reseedCounter > reseedInterval
+                            || pr) {
                         reseedAlgorithm(getEntropyInput(pr), ai);
                         ai = null;
                     }
--- a/jdk/src/java.base/share/classes/sun/security/provider/AbstractHashDrbg.java	Wed Jul 06 01:20:20 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/AbstractHashDrbg.java	Wed Jul 06 21:52:12 2016 +0800
@@ -27,7 +27,8 @@
 
 import sun.security.util.HexDumpEncoder;
 
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 public abstract class AbstractHashDrbg extends AbstractDrbg {
@@ -113,16 +114,13 @@
         // 800-90Ar1 10.1.2.3: Hmac_DRBG Instantiate Process.
 
         // Step 1: entropy_input || nonce || personalization_string.
-        byte[] seed = Arrays.copyOf(entropy, entropy.length + nonce.length +
-                ((personalizationString == null) ? 0
-                        : personalizationString.length));
-        System.arraycopy(nonce, 0, seed, entropy.length, nonce.length);
+        List<byte[]> inputs = new ArrayList<>(3);
+        inputs.add(entropy);
+        inputs.add(nonce);
         if (personalizationString != null) {
-            System.arraycopy(personalizationString, 0,
-                    seed, entropy.length + nonce.length,
-                    personalizationString.length);
+            inputs.add(personalizationString);
         }
-        hashReseedInternal(seed);
+        hashReseedInternal(inputs);
     }
 
     @Override
@@ -140,13 +138,17 @@
         // 800-90Ar1 10.1.2.4: Hmac_DRBG Reseed Process.
 
         // Step 1: entropy_input || additional_input.
+        List<byte[]> inputs = new ArrayList<>(2);
+        inputs.add(ei);
         if (additionalInput != null) {
-            ei = Arrays.copyOf(ei, ei.length + additionalInput.length);
-            System.arraycopy(additionalInput, 0, ei,
-                    ei.length - additionalInput.length, additionalInput.length);
+            inputs.add(additionalInput);
         }
-        hashReseedInternal(ei);
+        hashReseedInternal(inputs);
     }
 
-    protected abstract void hashReseedInternal(byte[] seed);
+    /**
+     * Operates on multiple inputs.
+     * @param inputs not null, each element neither null
+     */
+    protected abstract void hashReseedInternal(List<byte[]> inputs);
 }
--- a/jdk/src/java.base/share/classes/sun/security/provider/CtrDrbg.java	Wed Jul 06 01:20:20 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/CtrDrbg.java	Wed Jul 06 21:52:12 2016 +0800
@@ -242,6 +242,11 @@
             if (personalizationString == null) {
                 more = nonce;
             } else {
+                if (nonce.length + personalizationString.length < 0) {
+                    // Length must be represented as a 32 bit integer in df()
+                    throw new IllegalArgumentException(
+                            "nonce plus personalization string is too long");
+                }
                 more = Arrays.copyOf(
                         nonce, nonce.length + personalizationString.length);
                 System.arraycopy(personalizationString, 0, more, nonce.length,
@@ -256,50 +261,73 @@
         reseedAlgorithm(ei, more);
     }
 
+    /**
+     * Block_cipher_df in 10.3.2
+     *
+     * @param input the input string
+     * @return the output block (always of seedLen)
+     */
     private byte[] df(byte[] input) {
+        // 800-90Ar1 10.3.2
+        // 2. L = len (input_string)/8
         int l = input.length;
+        // 3. N = number_of_bits_to_return/8
         int n = seedLen;
-        int slen = 4 + 4 + l + 1;
-        byte[] s = new byte[(slen + blockLen - 1) / blockLen * blockLen];
-        s[0] = (byte)(l >> 24);
-        s[1] = (byte)(l >> 16);
-        s[2] = (byte)(l >> 8);
-        s[3] = (byte)(l);
-        s[4] = (byte)(n >> 24);
-        s[5] = (byte)(n >> 16);
-        s[6] = (byte)(n >> 8);
-        s[7] = (byte)(n);
-        System.arraycopy(input, 0, s, 8, l);
-        s[8+l] = (byte)0x80;
+        // 4. S = L || N || input_string || 0x80
+        byte[] ln = new byte[8];
+        ln[0] = (byte)(l >> 24);
+        ln[1] = (byte)(l >> 16);
+        ln[2] = (byte)(l >> 8);
+        ln[3] = (byte)(l);
+        ln[4] = (byte)(n >> 24);
+        ln[5] = (byte)(n >> 16);
+        ln[6] = (byte)(n >> 8);
+        ln[7] = (byte)(n);
 
+        // 5. Zero padding of S
+        // Not necessary, see bcc
+
+        // 8. K = leftmost (0x00010203...1D1E1F, keylen).
         byte[] k = new byte[keyLen];
         for (int i = 0; i < k.length; i++) {
             k[i] = (byte)i;
         }
 
+        // 6. temp = the Null String
         byte[] temp = new byte[seedLen];
 
+        // 7. i = 0
         for (int i = 0; i * blockLen < temp.length; i++) {
-            byte[] iv = new byte[blockLen + s.length];
+            // 9.1 IV = i || 0^(outlen - len (i)). outLen is blockLen
+            byte[] iv = new byte[blockLen];
             iv[0] = (byte)(i >> 24);
             iv[1] = (byte)(i >> 16);
             iv[2] = (byte)(i >> 8);
             iv[3] = (byte)(i);
-            System.arraycopy(s, 0, iv, blockLen, s.length);
+
             int tailLen = temp.length - blockLen*i;
             if (tailLen > blockLen) {
                 tailLen = blockLen;
             }
-            System.arraycopy(bcc(k, iv), 0, temp, blockLen*i, tailLen);
+            // 9.2 temp = temp || BCC (K, (IV || S)).
+            System.arraycopy(bcc(k, iv, ln, input, new byte[]{(byte)0x80}),
+                    0, temp, blockLen*i, tailLen);
         }
 
+        // 10. K = leftmost(temp, keylen)
         k = Arrays.copyOf(temp, keyLen);
+
+        // 11. x = select(temp, keylen+1, keylen+outlen)
         byte[] x = Arrays.copyOfRange(temp, keyLen, temp.length);
 
+        // 12. temp = the Null string
+        // No need to clean up, temp will be overwritten
+
         for (int i = 0; i * blockLen < seedLen; i++) {
             try {
                 cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg));
                 int tailLen = temp.length - blockLen*i;
+                // 14. requested_bits = leftmost(temp, nuumber_of_bits_to_return)
                 if (tailLen > blockLen) {
                     tailLen = blockLen;
                 }
@@ -309,21 +337,45 @@
                 throw new InternalError(e);
             }
         }
+
+        // 15. Return
         return temp;
     }
 
-    private byte[] bcc(byte[] k, byte[] data) {
+    /**
+     * Block_Encrypt in 10.3.3
+     *
+     * @param k the key
+     * @param data after concatenated, the data to be operated upon. This is
+     *             a series of byte[], each with an arbitrary length. Note
+     *             that the full length is not necessarily a multiple of
+     *             outlen. XOR with zero is no-op.
+     * @return the result
+     */
+    private byte[] bcc(byte[] k, byte[]... data) {
         byte[] chain = new byte[blockLen];
-        int n = data.length / blockLen;
-        for (int i = 0; i < n; i++) {
-            byte[] inputBlock = Arrays.copyOfRange(
-                    data, i * blockLen, i * blockLen + blockLen);
-            for (int j = 0; j < blockLen; j++) {
-                inputBlock[j] ^= chain[j];
+        int n1 = 0; // index in data
+        int n2 = 0; // index in data[n1]
+        // pack blockLen of bytes into chain from data[][], again and again
+        while (n1 < data.length) {
+            int j;
+            out: for (j = 0; j < blockLen; j++) {
+                while (n2 >= data[n1].length) {
+                    n1++;
+                    if (n1 >= data.length) {
+                        break out;
+                    }
+                    n2 = 0;
+                }
+                chain[j] ^= data[n1][n2];
+                n2++;
+            }
+            if (j == 0) { // all data happens to be consumed in the last loop
+                break;
             }
             try {
                 cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(k, keyAlg));
-                chain = cipher.doFinal(inputBlock);
+                chain = cipher.doFinal(chain);
             } catch (GeneralSecurityException e) {
                 throw new InternalError(e);
             }
@@ -341,6 +393,11 @@
 
             // Step 1: cat bytes
             if (additionalInput != null) {
+                if (ei.length + additionalInput.length < 0) {
+                    // Length must be represented as a 32 bit integer in df()
+                    throw new IllegalArgumentException(
+                            "entropy plus additional input is too long");
+                }
                 byte[] temp = Arrays.copyOf(
                         ei, ei.length + additionalInput.length);
                 System.arraycopy(additionalInput, 0, temp, ei.length,
@@ -430,10 +487,10 @@
 
         // Step 3. temp = Null
         int pos = 0;
+        int len = result.length;
 
         // Step 4. Loop
-        while (pos < result.length) {
-            int tailLen = result.length - pos;
+        while (len > 0) {
             // Step 4.1. Increment
             addOne(v, ctrLen);
             try {
@@ -443,10 +500,15 @@
 
                 // Step 4.3 and 5. Cat bytes and leftmost
                 System.arraycopy(out, 0, result, pos,
-                        (tailLen > blockLen) ? blockLen : tailLen);
+                        (len > blockLen) ? blockLen : len);
             } catch (GeneralSecurityException e) {
                 throw new InternalError(e);
             }
+            len -= blockLen;
+            if (len <= 0) {
+                // shortcut, so that pos needn't be updated
+                break;
+            }
             pos += blockLen;
         }
 
--- a/jdk/src/java.base/share/classes/sun/security/provider/HashDrbg.java	Wed Jul 06 01:20:20 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/HashDrbg.java	Wed Jul 06 21:52:12 2016 +0800
@@ -31,7 +31,9 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.SecureRandomParameters;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 public class HashDrbg extends AbstractHashDrbg {
 
@@ -70,7 +72,7 @@
         }
     }
 
-    private byte[] hashDf(int requested, byte[]... inputs) {
+    private byte[] hashDf(int requested, List<byte[]> inputs) {
         return hashDf(digest, outLen, requested, inputs);
     }
 
@@ -79,6 +81,9 @@
      * The function is used inside Hash_DRBG, and can also be used as an
      * approved conditioning function as described in 800-90B 6.4.2.2.
      *
+     * Note: In each current call, requested is seedLen, therefore small,
+     * no need to worry about overflow.
+     *
      * @param digest a {@code MessageDigest} object in reset state
      * @param outLen {@link MessageDigest#getDigestLength} of {@code digest}
      * @param requested requested output length, in bytes
@@ -86,12 +91,18 @@
      * @return the condensed/expanded output
      */
     public static byte[] hashDf(MessageDigest digest, int outLen,
-                                int requested, byte[]... inputs) {
+                                int requested, List<byte[]> inputs) {
+        // 1. temp = the Null string.
+        // 2. len = upper_int(no_of_bits_to_return / outLen)
         int len = (requested + outLen - 1) / outLen;
         byte[] temp = new byte[len * outLen];
+        // 3. counter = 0x01
         int counter = 1;
 
+        // 4. For i = 1 to len do
         for (int i=0; i<len; i++) {
+            // 4.1 temp = temp
+            //      || Hash (counter || no_of_bits_to_return || input_string).
             digest.update((byte) counter);
             digest.update((byte)(requested >> 21)); // requested*8 as int32
             digest.update((byte)(requested >> 13));
@@ -105,14 +116,17 @@
             } catch (DigestException e) {
                 throw new AssertionError("will not happen", e);
             }
+            // 4.2 counter = counter + 1
             counter++;
         }
+        // 5. requested_bits = leftmost (temp, no_of_bits_to_return).
         return temp.length == requested? temp: Arrays.copyOf(temp, requested);
+        // 6. Return
     }
 
     // This method is used by both instantiation and reseeding.
     @Override
-    protected final synchronized void hashReseedInternal(byte[] input) {
+    protected final synchronized void hashReseedInternal(List<byte[]> inputs) {
 
         // 800-90Ar1 10.1.1.2: Instantiate Process.
         // 800-90Ar1 10.1.1.3: Reseed Process.
@@ -121,16 +135,21 @@
         // Step 2: seed = Hash_df (seed_material, seedlen).
         if (v != null) {
             // Step 1 of 10.1.1.3: Prepend 0x01 || V
-            seed = hashDf(seedLen, ONE, v, input);
+            inputs.add(0, ONE);
+            inputs.add(1, v);
+            seed = hashDf(seedLen, inputs);
         } else {
-            seed = hashDf(seedLen, input);
+            seed = hashDf(seedLen, inputs);
         }
 
         // Step 3. V = seed.
         v = seed;
 
         // Step 4. C = Hash_df ((0x00 || V), seedlen).
-        c = hashDf(seedLen, ZERO, v);
+        inputs = new ArrayList<>(2);
+        inputs.add(ZERO);
+        inputs.add(v);
+        c = hashDf(seedLen, inputs);
 
         // Step 5. reseed_counter = 1.
         reseedCounter = 1;
@@ -197,7 +216,7 @@
         }
 
         // Step 3. Hashgen (requested_number_of_bits, V).
-        hashGen(result, result.length, v);
+        hashGen(result, v);
 
         // Step 4. H = Hash (0x03 || V).
         digest.update((byte)3);
@@ -222,10 +241,7 @@
     }
 
     // 800-90Ar1 10.1.1.4: Hashgen
-    private void hashGen(byte[] output, int len, byte[] v) {
-
-        // Step 1. m
-        int m = (len + outLen - 1) / outLen;
+    private void hashGen(byte[] output, byte[] v) {
 
         // Step 2. data = V
         byte[] data = v;
@@ -233,32 +249,36 @@
         // Step 3: W is output not filled
 
         // Step 4: For i = 1 to m
-        for (int i = 0; i < m; i++) {
-            int tailLen = len - i * outLen;
-            if (tailLen < outLen) {
+        int pos = 0;
+        int len = output.length;
+
+        while (len > 0) {
+            if (len < outLen) {
                 // Step 4.1 w = Hash (data).
                 // Step 4.2 W = W || w.
-                System.arraycopy(digest.digest(data), 0, output, i * outLen,
-                        tailLen);
+                System.arraycopy(digest.digest(data), 0, output, pos,
+                        len);
             } else {
                 try {
                     // Step 4.1 w = Hash (data).
                     digest.update(data);
                     // Step 4.2 digest into right position, no need to cat
-                    digest.digest(output, i*outLen, outLen);
+                    digest.digest(output, pos, outLen);
                 } catch (DigestException e) {
                     throw new AssertionError("will not happen", e);
                 }
             }
-            // Unless this is the last around, we will need to increment data.
-            // but we cannot change v, so a copy is made.
-            if (i != m - 1) {
-                if (data == v) {
-                    data = Arrays.copyOf(v, v.length);
-                }
-                // Step 4.3 data = (data + 1) mod 2^seedlen.
-                addBytes(data, seedLen, ONE);
+            len -= outLen;
+            if (len <= 0) {
+                // shortcut, so that data and pos needn't be updated
+                break;
             }
+            // Step 4.3 data = (data + 1) mod 2^seedlen.
+            if (data == v) {
+                data = Arrays.copyOf(v, v.length);
+            }
+            addBytes(data, seedLen, ONE);
+            pos += outLen;
         }
 
         // Step 5: No need to truncate
--- a/jdk/src/java.base/share/classes/sun/security/provider/HmacDrbg.java	Wed Jul 06 01:20:20 2016 -0700
+++ b/jdk/src/java.base/share/classes/sun/security/provider/HmacDrbg.java	Wed Jul 06 21:52:12 2016 +0800
@@ -32,6 +32,8 @@
 import java.security.NoSuchProviderException;
 import java.security.SecureRandomParameters;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 public class HmacDrbg extends AbstractHashDrbg {
 
@@ -56,7 +58,7 @@
     }
 
     // 800-90Ar1 10.1.2.2: HMAC_DRBG Update Process
-    private void update(byte[]... inputs) {
+    private void update(List<byte[]> inputs) {
         try {
             // Step 1. K = HMAC (K, V || 0x00 || provided_data).
             mac.init(new SecretKeySpec(k, macAlg));
@@ -71,7 +73,7 @@
             mac.init(new SecretKeySpec(k, macAlg));
             v = mac.doFinal(v);
 
-            if (inputs.length != 0) {
+            if (!inputs.isEmpty()) {
                 // Step 4. K = HMAC (K, V || 0x01 || provided_data).
                 mac.update(v);
                 mac.update((byte) 1);
@@ -116,7 +118,7 @@
 
     // This method is used by both instantiation and reseeding.
     @Override
-    protected final synchronized void hashReseedInternal(byte[] input) {
+    protected final synchronized void hashReseedInternal(List<byte[]> input) {
 
         // 800-90Ar1 10.1.2.3: Instantiate Process.
         // 800-90Ar1 10.1.2.4: Reseed Process.
@@ -156,16 +158,15 @@
 
         // Step 2. HMAC_DRBG_Update
         if (additionalInput != null) {
-            update(additionalInput);
+            update(Collections.singletonList(additionalInput));
         }
 
         // Step 3. temp = Null.
         int pos = 0;
+        int len = result.length;
 
         // Step 4. Loop
-        while (pos < result.length) {
-            int tailLen = result.length - pos;
-
+        while (len > 0) {
             // Step 4.1 V = HMAC (Key, V).
             try {
                 mac.init(new SecretKeySpec(k, macAlg));
@@ -175,7 +176,13 @@
             v = mac.doFinal(v);
             // Step 4.2 temp = temp || V.
             System.arraycopy(v, 0, result, pos,
-                    tailLen > outLen ? outLen : tailLen);
+                    len > outLen ? outLen : len);
+
+            len -= outLen;
+            if (len <= 0) {
+                // shortcut, so that pos needn't be updated
+                break;
+            }
             pos += outLen;
         }
 
@@ -183,9 +190,9 @@
 
         // Step 6. HMAC_DRBG_Update (additional_input, Key, V).
         if (additionalInput != null) {
-            update(additionalInput);
+            update(Collections.singletonList(additionalInput));
         } else {
-            update();
+            update(Collections.emptyList());
         }
 
         // Step 7. reseed_counter = reseed_counter + 1.