8158589: Possible integer overflow issues for DRBG

Reviewed-by: xuelei, mullan
This commit is contained in:
Weijun Wang 2016-07-06 21:52:12 +08:00
parent 6e722043e3
commit db8e727862
5 changed files with 170 additions and 78 deletions

View File

@ -377,11 +377,12 @@ public abstract class AbstractDrbg {
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;
}

View File

@ -27,7 +27,8 @@ package sun.security.provider;
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 @@ public abstract class AbstractHashDrbg extends AbstractDrbg {
// 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 @@ public abstract class AbstractHashDrbg extends AbstractDrbg {
// 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);
}

View File

@ -242,6 +242,11 @@ public class CtrDrbg extends AbstractDrbg {
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 @@ public class CtrDrbg extends AbstractDrbg {
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 @@ public class CtrDrbg extends AbstractDrbg {
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 @@ public class CtrDrbg extends AbstractDrbg {
// 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 @@ public class CtrDrbg extends AbstractDrbg {
// 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 @@ public class CtrDrbg extends AbstractDrbg {
// 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;
}

View File

@ -31,7 +31,9 @@ import java.security.MessageDigest;
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 @@ public class HashDrbg extends AbstractHashDrbg {
}
}
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 @@ public class HashDrbg extends AbstractHashDrbg {
* 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 @@ public class HashDrbg extends AbstractHashDrbg {
* @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 @@ public class HashDrbg extends AbstractHashDrbg {
} 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 @@ public class HashDrbg extends AbstractHashDrbg {
// 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 @@ public class HashDrbg extends AbstractHashDrbg {
}
// 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 @@ public class HashDrbg extends AbstractHashDrbg {
}
// 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 @@ public class HashDrbg extends AbstractHashDrbg {
// 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

View File

@ -32,6 +32,8 @@ import java.security.NoSuchAlgorithmException;
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 @@ public class HmacDrbg extends AbstractHashDrbg {
}
// 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 @@ public class HmacDrbg extends AbstractHashDrbg {
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 @@ public class HmacDrbg extends AbstractHashDrbg {
// 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 @@ public class HmacDrbg extends AbstractHashDrbg {
// 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 @@ public class HmacDrbg extends AbstractHashDrbg {
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 @@ public class HmacDrbg extends AbstractHashDrbg {
// 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.