diff options
author | evitayan <evitayan@google.com> | 2020-04-22 14:24:30 -0700 |
---|---|---|
committer | evitayan <evitayan@google.com> | 2020-04-24 19:13:30 -0700 |
commit | ffb24e8ed3ef96fb335d33ed16d6ed3213ad7974 (patch) | |
tree | 3de956a92781b9928700c0ea21b012837c30214c | |
parent | fe1329b1637308e570acc9656388425334722460 (diff) | |
download | ike-ffb24e8ed3ef96fb335d33ed16d6ed3213ad7974.tar.gz |
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
3 files changed, 225 insertions, 4 deletions
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 f06f1512..4692dfa2 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 @@ public final class IkeKePayload extends IkePayload { /** * 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 @@ public final class IkeKePayload extends IkePayload { 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 00000000..e09fa0be --- /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 00000000..4ff1c12c --- /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()); + } +} |