Create deterministic secure random to support IKE test mode

This CL also adds another IkeKePayload constructor for testing
that the deterministic secure random works for DH exchange

Bug: 148689509
Test: atest FrameworksIkeTests(new tests added)
Change-Id: Ic6ed919d13416dcd1015602634ea11c302eaa745
diff --git a/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayload.java b/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayload.java
index f06f151..4692dfa 100644
--- a/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayload.java
+++ b/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayload.java
@@ -140,16 +140,40 @@
     /**
      * Construct an instance of IkeKePayload for building an outbound packet.
      *
-     * <p>Generate a DH key pair. Cache the private key and and send out the public key as
+     * <p>Generate a DH key pair. Cache the private key and send out the public key as
      * keyExchangeData.
      *
      * <p>Critical bit in this payload must not be set as instructed in RFC 7296.
      *
+     * <p>TODO(b/148689509): Remove this constructor in the following CL that injects
+     * DeterministicSecureRandom to the process of IKE Session creation.
+     *
      * @param dh DH group for this KE payload
      * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange
      *     Protocol Version 2 (IKEv2), Critical.
      */
     public IkeKePayload(@SaProposal.DhGroup int dh) {
+        this(dh, null /* secureRandom */);
+    }
+
+    /**
+     * Construct an instance of IkeKePayload for building an outbound packet.
+     *
+     * <p>Generate a DH key pair. Cache the private key and send out the public key as
+     * keyExchangeData.
+     *
+     * <p>Critical bit in this payload must not be set as instructed in RFC 7296.
+     *
+     * <p>TODO(b/148689509): Pass in a SecureRandom factory in the following CL that injects
+     * DeterministicSecureRandom to the process of IKE Session creation.
+     *
+     * @param dh DH group for this KE payload
+     * @param secureRandom the source of secure randomness, if null, a SecureRandom will be
+     *     constructed internally
+     * @see <a href="https://tools.ietf.org/html/rfc7296#page-76">RFC 7296, Internet Key Exchange
+     *     Protocol Version 2 (IKEv2), Critical.
+     */
+    public IkeKePayload(@SaProposal.DhGroup int dh, SecureRandom secureRandom) {
         super(PAYLOAD_TYPE_KE, false);
 
         dhGroup = dh;
@@ -191,9 +215,9 @@
             DHParameterSpec dhParams = new DHParameterSpec(prime, baseGen);
 
             KeyPairGenerator dhKeyPairGen = KeyPairGenerator.getInstance(KEY_EXCHANGE_ALGORITHM);
-            // By default SecureRandom uses AndroidOpenSSL provided SHA1PRNG Algorithm, which takes
-            // /dev/urandom as seed source.
-            dhKeyPairGen.initialize(dhParams, new SecureRandom());
+
+            SecureRandom random = secureRandom == null ? new SecureRandom() : secureRandom;
+            dhKeyPairGen.initialize(dhParams, random);
 
             KeyPair keyPair = dhKeyPairGen.generateKeyPair();
 
diff --git a/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandom.java b/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandom.java
new file mode 100644
index 0000000..e09fa0b
--- /dev/null
+++ b/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandom.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.net.ipsec.ike.testmode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.crypto.KeyGenerationUtils;
+import com.android.internal.util.HexDump;
+
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Deterministic SecureRandom to support test mode IKE library.
+ *
+ * <p>Deterministic SecureRandom MUST only be used in test mode. It allows IKE library to do IKE
+ * Session negotiation with pre captured responder packets as well as always generates the same set
+ * of crypto keys.
+ *
+ * <p>This deterministic SecureRandom is deterministic in the way that if caller constructs multiple
+ * instances and call the same methods from these instances, all the outputs will be the same. This
+ * class is random in the way that, from the same intance, caller will get different outputs by
+ * calling the same method multiple times.
+ */
+@VisibleForTesting
+public class DeterministicSecureRandom extends SecureRandom
+        implements KeyGenerationUtils.ByteSigner {
+    private static final String TAG = DeterministicSecureRandom.class.getSimpleName();
+
+    private static final String MAC_SHA256_NAME = "HmacSHA256";
+    private static final String MAC_SHA256_KEY_HEX =
+            "5D00F680E84F96374FC1BF8A4FC5F711467CBC62DF81A3B6169812531DF13E6C";
+    private static final String INITIAL_BYTE_TO_SIGN_HEX =
+            "2514A9C2B797BDDC50A1975A00866C3CC87190C29DCEBB228A4D8730AF8881BC";
+
+    private final Mac mByteSignerMac;
+
+    private byte[] mBytesToSign;
+
+    @VisibleForTesting
+    public DeterministicSecureRandom() {
+        super(null /*secureRandomSpi*/, null /*provider*/);
+
+        try {
+            mByteSignerMac = Mac.getInstance(MAC_SHA256_NAME);
+            byte[] byteSignerKey = HexDump.hexStringToByteArray(MAC_SHA256_KEY_HEX);
+            mByteSignerMac.init(new SecretKeySpec(byteSignerKey, MAC_SHA256_NAME));
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            // Should never happen
+            throw new IllegalArgumentException("Failed to construct DeterministicSecureRandom", e);
+        }
+
+        mBytesToSign = HexDump.hexStringToByteArray(INITIAL_BYTE_TO_SIGN_HEX);
+    }
+
+    @Override
+    public byte[] signBytes(byte[] keyBytes, byte[] dataToSign) {
+        mByteSignerMac.update(dataToSign);
+        return mByteSignerMac.doFinal();
+    }
+
+    private byte[] generateBytes(int numBytes) {
+        mBytesToSign =
+                KeyGenerationUtils.prfPlus(
+                        this, null /* keyBytes; unused */, mBytesToSign, numBytes);
+        return mBytesToSign;
+    }
+
+    @Override
+    public byte[] generateSeed(int numBytes) {
+        return generateBytes(numBytes);
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return TAG;
+    }
+
+    // This method serves to provide a source of random bits to all of the method inherited from
+    // {@link Random} (for example, {@link nextInt}, {@link nextLong}).
+    @Override
+    public void nextBytes(byte[] bytes) {
+        ByteBuffer buffer = ByteBuffer.wrap(generateBytes(bytes.length));
+        buffer.rewind();
+        buffer.get(bytes);
+    }
+
+    @Override
+    public void setSeed(byte[] seed) {
+        // Do nothing
+    }
+
+    @Override
+    public void setSeed(long seed) {
+        // Do nothing
+    }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandomTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandomTest.java
new file mode 100644
index 0000000..4ff1c12
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testmode/DeterministicSecureRandomTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.net.ipsec.ike.testmode;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.internal.net.ipsec.ike.message.IkeKePayload;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import javax.crypto.spec.DHPrivateKeySpec;
+
+public final class DeterministicSecureRandomTest {
+    private static final int BYTE_ARRAY_LEN = 20;
+
+    @Test
+    public void testDeterministicSecureRandomNonRepeating() throws Exception {
+        DeterministicSecureRandom secureRandom = new DeterministicSecureRandom();
+
+        assertFalse(
+                Arrays.equals(
+                        secureRandom.generateSeed(BYTE_ARRAY_LEN),
+                        secureRandom.generateSeed(BYTE_ARRAY_LEN)));
+        assertNotEquals(secureRandom.nextLong(), secureRandom.nextLong());
+
+        byte[] randomBytesOne = new byte[BYTE_ARRAY_LEN];
+        byte[] randomBytesTwo = new byte[BYTE_ARRAY_LEN];
+        secureRandom.nextBytes(randomBytesOne);
+        secureRandom.nextBytes(randomBytesTwo);
+        assertFalse(Arrays.equals(randomBytesOne, randomBytesTwo));
+    }
+
+    @Test
+    public void testTwoDeterministicSecureRandomsAreDeterministic() throws Exception {
+        DeterministicSecureRandom srOne = new DeterministicSecureRandom();
+        DeterministicSecureRandom srTwo = new DeterministicSecureRandom();
+
+        assertArrayEquals(srOne.generateSeed(BYTE_ARRAY_LEN), srTwo.generateSeed(BYTE_ARRAY_LEN));
+        assertEquals(srOne.nextLong(), srTwo.nextLong());
+
+        byte[] randomBytesOne = new byte[BYTE_ARRAY_LEN];
+        byte[] randomBytesTwo = new byte[BYTE_ARRAY_LEN];
+        srOne.nextBytes(randomBytesOne);
+        srTwo.nextBytes(randomBytesTwo);
+        assertArrayEquals(randomBytesOne, randomBytesTwo);
+    }
+
+    @Test
+    public void testDeterministicSecureRandomInKePayload() throws Exception {
+        DeterministicSecureRandom srOne = new DeterministicSecureRandom();
+        DeterministicSecureRandom srTwo = new DeterministicSecureRandom();
+
+        IkeKePayload kePayloadOne = new IkeKePayload(DH_GROUP_2048_BIT_MODP, srOne);
+        IkeKePayload kePayloadTwo = new IkeKePayload(DH_GROUP_2048_BIT_MODP, srTwo);
+        assertArrayEquals(kePayloadOne.keyExchangeData, kePayloadTwo.keyExchangeData);
+
+        DHPrivateKeySpec localPrivateKeyOne = kePayloadOne.localPrivateKey;
+        DHPrivateKeySpec localPrivateKeyTwo = kePayloadTwo.localPrivateKey;
+        assertEquals(localPrivateKeyOne.getG(), localPrivateKeyTwo.getG());
+        assertEquals(localPrivateKeyOne.getP(), localPrivateKeyTwo.getP());
+        assertEquals(localPrivateKeyOne.getX(), localPrivateKeyTwo.getX());
+    }
+}