aboutsummaryrefslogtreecommitdiff
path: root/tests/iketests/src/java/com/android/internal/net/ipsec
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2019-11-11 21:19:18 +0000
commitdb74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch)
treeb39c3e794945072d38b97c963c5a6f10753514e1 /tests/iketests/src/java/com/android/internal/net/ipsec
parent7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff)
parenteb4c77d7228f956f928c7d3500a220339ee78388 (diff)
downloadike-db74d7937f3a9503f0e2590ec24c3bcfbf259ce0.tar.gz
Snap for 6001391 from eb4c77d7228f956f928c7d3500a220339ee78388 to qt-aml-tzdata-release
Change-Id: Ic6fc3b152696e0096d00036f460262a8b0319394
Diffstat (limited to 'tests/iketests/src/java/com/android/internal/net/ipsec')
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java1646
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java130
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java4036
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java385
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java336
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java160
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java128
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java187
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java164
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java161
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java133
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java144
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java154
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java147
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java631
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java264
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java68
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java489
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java140
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java210
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java200
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java935
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java49
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java249
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java927
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java108
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java201
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java71
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java143
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java63
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java91
-rw-r--r--tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java129
32 files changed, 12879 insertions, 0 deletions
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
new file mode 100644
index 00000000..f6c32eef
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/ChildSessionStateMachineTest.java
@@ -0,0 +1,1646 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+import static android.system.OsConstants.AF_INET;
+
+import static com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CMD_FORCE_TRANSITION;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_DELETE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_KE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NONCE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_INITIATOR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_TS_RESPONDER;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_ESP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.LinkAddress;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.ChildSessionOptions;
+import android.net.ipsec.ike.IkeManager;
+import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TunnelModeChildSessionOptions;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.CreateChildSaHelper;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig;
+import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Address;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Netmask;
+import com.android.internal.net.ipsec.ike.message.IkeDeletePayload;
+import com.android.internal.net.ipsec.ike.message.IkeKePayload;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeNoncePayload;
+import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload;
+import com.android.internal.net.ipsec.ike.message.IkePayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.message.IkeTestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.utils.Log;
+import com.android.server.IpSecService;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public final class ChildSessionStateMachineTest {
+ private static final String TAG = "ChildSessionStateMachineTest";
+
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+ private static final Inet4Address INTERNAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("203.0.113.100"));
+
+ private static final int IPV4_PREFIX_LEN = 32;
+
+ private static final String IKE_AUTH_RESP_SA_PAYLOAD =
+ "2c00002c0000002801030403cae7019f0300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_RESP_SA_PAYLOAD =
+ "2800002c0000002801030403cd1736b30300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_REQ_SA_PAYLOAD =
+ "2800002c0000002801030403c88336490300000c0100000c800e0080"
+ + "03000008030000020000000805000000";
+ private static final String REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD =
+ "2800002c0000002801030403c88336490300000c0100000c800e00c0"
+ + "03000008030000020000000805000000";
+
+ private static final int CURRENT_CHILD_SA_SPI_IN = 0x2ad4c0a2;
+ private static final int CURRENT_CHILD_SA_SPI_OUT = 0xcae7019f;
+
+ private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_IN = 0x57a09b0f;
+ private static final int LOCAL_INIT_NEW_CHILD_SA_SPI_OUT = 0xcd1736b3;
+
+ private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_IN = 0xd2d01795;
+ private static final int REMOTE_INIT_NEW_CHILD_SA_SPI_OUT = 0xc8833649;
+
+ private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6";
+ private static final byte[] SK_D = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING);
+
+ private static final int KEY_LEN_IKE_SKD = 20;
+
+ private IkeMacPrf mIkePrf;
+
+ private Context mContext;
+ private IpSecService mMockIpSecService;
+ private IpSecManager mMockIpSecManager;
+ private UdpEncapsulationSocket mMockUdpEncapSocket;
+
+ private TestLooper mLooper;
+ private ChildSessionStateMachine mChildSessionStateMachine;
+
+ private List<IkePayload> mFirstSaReqPayloads = new LinkedList<>();
+ private List<IkePayload> mFirstSaRespPayloads = new LinkedList<>();
+
+ private ChildSaRecord mSpyCurrentChildSaRecord;
+ private ChildSaRecord mSpyLocalInitNewChildSaRecord;
+ private ChildSaRecord mSpyRemoteInitNewChildSaRecord;
+
+ private Log mSpyIkeLog;
+
+ private ISaRecordHelper mMockSaRecordHelper;
+
+ private ChildSessionOptions mChildSessionOptions;
+ private EncryptionTransform mChildEncryptionTransform;
+ private IntegrityTransform mChildIntegrityTransform;
+ private DhGroupTransform mChildDhGroupTransform;
+
+ private ChildSaProposal mMockNegotiatedProposal;
+
+ private Executor mSpyUserCbExecutor;
+ private ChildSessionCallback mMockChildSessionCallback;
+ private IChildSessionSmCallback mMockChildSessionSmCallback;
+
+ private ArgumentCaptor<ChildSaRecordConfig> mChildSaRecordConfigCaptor =
+ ArgumentCaptor.forClass(ChildSaRecordConfig.class);
+ private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ private ArgumentCaptor<ChildSessionConfiguration> mChildConfigCaptor =
+ ArgumentCaptor.forClass(ChildSessionConfiguration.class);
+
+ private ArgumentMatcher<ChildLocalRequest> mRekeyChildLocalReqMatcher =
+ (argument) -> {
+ return CMD_LOCAL_REQUEST_REKEY_CHILD == argument.procedureType
+ && mMockChildSessionCallback == argument.childSessionCallback;
+ };
+
+ public ChildSessionStateMachineTest() {
+ mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
+ mMockChildSessionSmCallback = mock(IChildSessionSmCallback.class);
+
+ mChildEncryptionTransform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128);
+ mChildIntegrityTransform =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ mChildDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP);
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG);
+ IkeManager.setIkeLog(mSpyIkeLog);
+
+ mIkePrf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+
+ mContext = InstrumentationRegistry.getContext();
+ mMockIpSecService = mock(IpSecService.class);
+ mMockIpSecManager = new IpSecManager(mContext, mMockIpSecService);
+ mMockUdpEncapSocket = mock(UdpEncapsulationSocket.class);
+
+ mMockNegotiatedProposal = mock(ChildSaProposal.class);
+
+ mSpyUserCbExecutor =
+ spy(
+ (command) -> {
+ command.run();
+ });
+
+ mMockChildSessionCallback = mock(ChildSessionCallback.class);
+ mChildSessionOptions = buildChildSessionOptions();
+
+ // Setup thread and looper
+ mLooper = new TestLooper();
+ mChildSessionStateMachine =
+ new ChildSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mMockIpSecManager,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockChildSessionCallback,
+ mMockChildSessionSmCallback);
+ mChildSessionStateMachine.setDbg(true);
+ SaRecord.setSaRecordHelper(mMockSaRecordHelper);
+
+ setUpFirstSaNegoPayloadLists();
+ setUpChildSaRecords();
+
+ mChildSessionStateMachine.start();
+ }
+
+ @After
+ public void tearDown() {
+ mChildSessionStateMachine.setDbg(false);
+ IkeManager.resetIkeLog();
+ SaRecord.setSaRecordHelper(new SaRecordHelper());
+ }
+
+ private ChildSaProposal buildSaProposal() throws Exception {
+ return new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .build();
+ }
+
+ private ChildSessionOptions buildChildSessionOptions() throws Exception {
+ return new TunnelModeChildSessionOptions.Builder()
+ .addSaProposal(buildSaProposal())
+ .addInternalAddressRequest(AF_INET, 1)
+ .addInternalAddressRequest(INTERNAL_ADDRESS, IPV4_PREFIX_LEN)
+ .build();
+ }
+
+ private void setUpChildSaRecords() {
+ mSpyCurrentChildSaRecord =
+ makeSpyChildSaRecord(CURRENT_CHILD_SA_SPI_IN, CURRENT_CHILD_SA_SPI_OUT);
+ mSpyLocalInitNewChildSaRecord =
+ makeSpyChildSaRecord(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_IN, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+ mSpyRemoteInitNewChildSaRecord =
+ makeSpyChildSaRecord(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_IN, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT);
+ }
+
+ private void setUpSpiResource(InetAddress address, int spiRequested) throws Exception {
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(address.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(spiRequested));
+ }
+
+ private void setUpFirstSaNegoPayloadLists() throws Exception {
+ // Build locally generated SA payload that has its SPI resource allocated.
+ setUpSpiResource(LOCAL_ADDRESS, CURRENT_CHILD_SA_SPI_IN);
+ IkeSaPayload reqSaPayload =
+ IkeSaPayload.createChildSaRequestPayload(
+ mChildSessionOptions.getSaProposals(), mMockIpSecManager, LOCAL_ADDRESS);
+ mFirstSaReqPayloads.add(reqSaPayload);
+
+ // Build a remotely generated SA payload whoes SPI resource has not been allocated.
+ setUpSpiResource(REMOTE_ADDRESS, CURRENT_CHILD_SA_SPI_OUT);
+ IkeSaPayload respSaPayload =
+ (IkeSaPayload)
+ (IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA, true, IKE_AUTH_RESP_SA_PAYLOAD));
+ mFirstSaRespPayloads.add(respSaPayload);
+
+ // Build TS Payloads
+ IkeTsPayload tsInitPayload =
+ new IkeTsPayload(
+ true /*isInitiator*/, mChildSessionOptions.getLocalTrafficSelectors());
+ IkeTsPayload tsRespPayload =
+ new IkeTsPayload(
+ false /*isInitiator*/, mChildSessionOptions.getRemoteTrafficSelectors());
+
+ mFirstSaReqPayloads.add(tsInitPayload);
+ mFirstSaReqPayloads.add(tsRespPayload);
+ mFirstSaRespPayloads.add(tsInitPayload);
+ mFirstSaRespPayloads.add(tsRespPayload);
+
+ // Build Nonce Payloads
+ mFirstSaReqPayloads.add(new IkeNoncePayload());
+ mFirstSaRespPayloads.add(new IkeNoncePayload());
+
+ // Build Config Request Payload
+ List<ConfigAttribute> attrReqList = new LinkedList<>();
+ attrReqList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS));
+ attrReqList.add(new ConfigAttributeIpv4Netmask());
+ mFirstSaReqPayloads.add(new IkeConfigPayload(false /*isReply*/, attrReqList));
+
+ // Build Config Reply Payload
+ List<ConfigAttribute> attrRespList = new LinkedList<>();
+ attrRespList.add(new ConfigAttributeIpv4Address(INTERNAL_ADDRESS));
+ mFirstSaRespPayloads.add(new IkeConfigPayload(true /*isReply*/, attrRespList));
+ }
+
+ private ChildSaRecord makeSpyChildSaRecord(int inboundSpi, int outboundSpi) {
+ ChildSaRecord child =
+ spy(
+ new ChildSaRecord(
+ inboundSpi,
+ outboundSpi,
+ true /*localInit*/,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ mock(IpSecTransform.class),
+ mock(IpSecTransform.class),
+ mock(ChildLocalRequest.class)));
+ doNothing().when(child).close();
+ return child;
+ }
+
+ private void quitAndVerify() {
+ mChildSessionStateMachine.mCurrentChildSaRecord = null;
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = null;
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = null;
+
+ reset(mMockChildSessionSmCallback);
+ mChildSessionStateMachine.quit();
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback);
+ }
+
+ private void verifyChildSaRecordConfig(
+ ChildSaRecordConfig childSaRecordConfig,
+ int initSpi,
+ int respSpi,
+ boolean isLocalInit) {
+ assertEquals(mContext, childSaRecordConfig.context);
+ assertEquals(initSpi, childSaRecordConfig.initSpi.getSpi());
+ assertEquals(respSpi, childSaRecordConfig.respSpi.getSpi());
+
+ if (isLocalInit) {
+ assertEquals(LOCAL_ADDRESS, childSaRecordConfig.initAddress);
+ assertEquals(REMOTE_ADDRESS, childSaRecordConfig.respAddress);
+ } else {
+ assertEquals(REMOTE_ADDRESS, childSaRecordConfig.initAddress);
+ assertEquals(LOCAL_ADDRESS, childSaRecordConfig.respAddress);
+ }
+
+ assertEquals(mMockUdpEncapSocket, childSaRecordConfig.udpEncapSocket);
+ assertEquals(mIkePrf, childSaRecordConfig.ikePrf);
+ assertArrayEquals(SK_D, childSaRecordConfig.skD);
+ assertFalse(childSaRecordConfig.isTransport);
+ assertEquals(isLocalInit, childSaRecordConfig.isLocalInit);
+ assertTrue(childSaRecordConfig.hasIntegrityAlgo);
+ assertEquals(
+ CMD_LOCAL_REQUEST_REKEY_CHILD, childSaRecordConfig.futureRekeyEvent.procedureType);
+ assertEquals(
+ mMockChildSessionCallback,
+ childSaRecordConfig.futureRekeyEvent.childSessionCallback);
+ }
+
+ private void verifyNotifyUsersCreateIpSecSa(
+ ChildSaRecord childSaRecord, boolean expectInbound) {
+ IpSecTransform transform =
+ expectInbound
+ ? childSaRecord.getInboundIpSecTransform()
+ : childSaRecord.getOutboundIpSecTransform();
+ int direction = expectInbound ? IpSecManager.DIRECTION_IN : IpSecManager.DIRECTION_OUT;
+
+ verify(mMockChildSessionCallback).onIpSecTransformCreated(eq(transform), eq(direction));
+ }
+
+ private void verifyInitCreateChildResp(
+ List<IkePayload> reqPayloads, List<IkePayload> respPayloads) throws Exception {
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ mSpyCurrentChildSaRecord.getRemoteSpi(), mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Validate negotiated SA proposal.
+ ChildSaProposal negotiatedProposal = mChildSessionStateMachine.mSaProposal;
+ assertNotNull(negotiatedProposal);
+ assertEquals(
+ new EncryptionTransform[] {mChildEncryptionTransform},
+ negotiatedProposal.getEncryptionTransforms());
+ assertEquals(
+ new IntegrityTransform[] {mChildIntegrityTransform},
+ negotiatedProposal.getIntegrityTransforms());
+
+ // Validate current ChildSaRecord
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ eq(reqPayloads), eq(respPayloads), mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ CURRENT_CHILD_SA_SPI_IN,
+ CURRENT_CHILD_SA_SPI_OUT,
+ true /*isLocalInit*/);
+
+ assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Verify users have been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, true /*expectInbound*/);
+ verifyNotifyUsersCreateIpSecSa(mSpyCurrentChildSaRecord, false /*expectInbound*/);
+ verify(mMockChildSessionCallback).onOpened(mChildConfigCaptor.capture());
+
+ // Verify Child Session Configuration
+ ChildSessionConfiguration sessionConfig = mChildConfigCaptor.getValue();
+ verifyTsList(
+ Arrays.asList(mChildSessionOptions.getLocalTrafficSelectors()),
+ sessionConfig.getInboundTrafficSelectors());
+ verifyTsList(
+ Arrays.asList(mChildSessionOptions.getRemoteTrafficSelectors()),
+ sessionConfig.getOutboundTrafficSelectors());
+
+ List<LinkAddress> addrList = sessionConfig.getInternalAddressList();
+ assertEquals(1, addrList.size());
+ assertEquals(INTERNAL_ADDRESS, addrList.get(0).getAddress());
+ assertEquals(IPV4_PREFIX_LEN, addrList.get(0).getPrefixLength());
+ }
+
+ private void verifyTsList(
+ List<IkeTrafficSelector> expectedList, List<IkeTrafficSelector> tsList) {
+ assertEquals(expectedList.size(), tsList.size());
+ for (int i = 0; i < expectedList.size(); i++) {
+ assertEquals(expectedList.get(i), tsList.get(i));
+ }
+ }
+
+ @Test
+ public void testCreateFirstChild() throws Exception {
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenReturn(mSpyCurrentChildSaRecord);
+
+ mChildSessionStateMachine.handleFirstChildExchange(
+ mFirstSaReqPayloads,
+ mFirstSaRespPayloads,
+ LOCAL_ADDRESS,
+ REMOTE_ADDRESS,
+ mMockUdpEncapSocket,
+ mIkePrf,
+ SK_D);
+ mLooper.dispatchAll();
+
+ verifyInitCreateChildResp(mFirstSaReqPayloads, mFirstSaRespPayloads);
+
+ quitAndVerify();
+ }
+
+ private void verifyOutboundCreatePayloadTypes(
+ List<IkePayload> outboundPayloads, boolean isRekey) {
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class, outboundPayloads));
+ assertNotNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_NONCE, IkeNoncePayload.class, outboundPayloads));
+ assertNull(
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_KE, IkeKePayload.class, outboundPayloads));
+
+ IkeConfigPayload configPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_CP, IkeConfigPayload.class, outboundPayloads);
+ if (isRekey) {
+ assertNull(configPayload);
+ } else {
+ assertNotNull(configPayload);
+ assertEquals(IkeConfigPayload.CONFIG_TYPE_REQUEST, configPayload.configType);
+ }
+ }
+
+ @Test
+ public void testCreateChild() throws Exception {
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenReturn(mSpyCurrentChildSaRecord);
+
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Validate outbound payload list
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(false),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(reqPayloadList, false /*isRekey*/);
+ assertTrue(
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, reqPayloadList)
+ .isEmpty());
+
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads);
+ mLooper.dispatchAll();
+
+ verifyInitCreateChildResp(reqPayloadList, mFirstSaRespPayloads);
+
+ quitAndVerify();
+ }
+
+ private <T extends IkeException> void verifyHandleFatalErrorAndQuit(Class<T> exceptionClass) {
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mMockChildSessionSmCallback).onProcedureFinished(mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSessionClosed(mMockChildSessionCallback);
+
+ verify(mMockChildSessionCallback).onClosedExceptionally(any(exceptionClass));
+ }
+
+ @Test
+ public void testCreateChildHandlesErrorNotifyResp() throws Exception {
+ // Send out Create request
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Receive error notification in Create response
+ IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ respPayloads.add(notifyPayload);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(NoValidProposalChosenException.class);
+ }
+
+ @Test
+ public void testCreateChildHandlesRespWithMissingPayload() throws Exception {
+ // Send out Create request
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ // Receive response with no Nonce Payload
+ List<IkePayload> respPayloads = new LinkedList<>();
+ for (IkePayload payload : mFirstSaRespPayloads) {
+ if (IkePayload.PAYLOAD_TYPE_NONCE == payload.payloadType) continue;
+ respPayloads.add(payload);
+ }
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT);
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testCreateChildHandlesKeyCalculationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException("testCreateChildHandlesKeyCalculationFail"));
+
+ // Send out and receive Create Child message
+ mChildSessionStateMachine.createChildSession(
+ LOCAL_ADDRESS, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_CREATE_CHILD_SA, mFirstSaRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(CURRENT_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(CURRENT_CHILD_SA_SPI_OUT);
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ }
+
+ private void setupIdleStateMachine() throws Exception {
+ mChildSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
+ mChildSessionStateMachine.mRemoteAddress = REMOTE_ADDRESS;
+ mChildSessionStateMachine.mUdpEncapSocket = mMockUdpEncapSocket;
+ mChildSessionStateMachine.mIkePrf = mIkePrf;
+ mChildSessionStateMachine.mSkD = SK_D;
+
+ mChildSessionStateMachine.mSaProposal = buildSaProposal();
+ mChildSessionStateMachine.mChildCipher = mock(IkeCipher.class);
+ mChildSessionStateMachine.mChildIntegrity = mock(IkeMacIntegrity.class);
+ mChildSessionStateMachine.mLocalTs = mChildSessionOptions.getLocalTrafficSelectors();
+ mChildSessionStateMachine.mRemoteTs = mChildSessionOptions.getRemoteTrafficSelectors();
+
+ mChildSessionStateMachine.mCurrentChildSaRecord = mSpyCurrentChildSaRecord;
+
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ private List<IkePayload> makeDeletePayloads(int spi) {
+ List<IkePayload> inboundPayloads = new ArrayList<>(1);
+ inboundPayloads.add(new IkeDeletePayload(new int[] {spi}));
+ return inboundPayloads;
+ }
+
+ private void verifyOutboundDeletePayload(int expectedSpi, boolean isResp) {
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(isResp),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ List<IkePayload> outPayloadList = mPayloadListCaptor.getValue();
+ assertEquals(1, outPayloadList.size());
+
+ List<IkeDeletePayload> deletePayloads =
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_DELETE, IkeDeletePayload.class, outPayloadList);
+ assertEquals(1, deletePayloads.size());
+ IkeDeletePayload deletePayload = deletePayloads.get(0);
+ assertEquals(expectedSpi, deletePayload.spisToDelete[0]);
+ }
+
+ private void verifyNotifyUserDeleteChildSa(ChildSaRecord childSaRecord) {
+ verify(mMockChildSessionCallback)
+ .onIpSecTransformDeleted(
+ eq(childSaRecord.getInboundIpSecTransform()),
+ eq(IpSecManager.DIRECTION_IN));
+ verify(mMockChildSessionCallback)
+ .onIpSecTransformDeleted(
+ eq(childSaRecord.getOutboundIpSecTransform()),
+ eq(IpSecManager.DIRECTION_OUT));
+ }
+
+ private void verifyNotifyUsersDeleteSession() {
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback).onClosed();
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testDeleteChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ // Test initiating Delete request
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.DeleteChildLocalDelete);
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/);
+
+ // Test receiving Delete response
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ @Test
+ public void testDeleteChildLocalHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Test initiating Delete request
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ // Test receiving response with no Delete Payload
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>());
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mMockChildSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testDeleteChildLocalInInitial() throws Exception {
+ mChildSessionStateMachine.deleteChildSession();
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testSimultaneousDeleteChild() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.deleteChildSession();
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ assertTrue(respPayloadList.isEmpty());
+
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_INFORMATIONAL, new LinkedList<>());
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ @Test
+ public void testReplyRekeyRequestDuringDeletion() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.deleteChildSession();
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, mock(List.class));
+ mLooper.dispatchAll();
+
+ // Verify outbound response to Rekey Child request
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ assertEquals(1, respPayloadList.size());
+
+ IkeNotifyPayload notifyPayload = (IkeNotifyPayload) respPayloadList.get(0);
+ assertEquals(ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType);
+ assertEquals(0, notifyPayload.notifyData.length);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.DeleteChildLocalDelete);
+ }
+
+ @Test
+ public void testDeleteChildRemote() throws Exception {
+ setupIdleStateMachine();
+
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+ // Verify response
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+
+ assertEquals(1, respPayloadList.size());
+ assertArrayEquals(
+ new int[] {mSpyCurrentChildSaRecord.getLocalSpi()},
+ ((IkeDeletePayload) respPayloadList.get(0)).spisToDelete);
+
+ verifyNotifyUsersDeleteSession();
+ }
+
+ private void verifyOutboundRekeySaPayload(List<IkePayload> outboundPayloads, boolean isResp) {
+ IkeSaPayload saPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ PAYLOAD_TYPE_SA, IkeSaPayload.class, outboundPayloads);
+ assertEquals(isResp, saPayload.isSaResponse);
+ assertEquals(1, saPayload.proposalList.size());
+
+ IkeSaPayload.ChildProposal proposal =
+ (IkeSaPayload.ChildProposal) saPayload.proposalList.get(0);
+ assertEquals(1, proposal.number); // Must be 1-indexed
+ assertEquals(mChildSessionStateMachine.mSaProposal, proposal.saProposal);
+ }
+
+ private void verifyOutboundRekeyNotifyPayload(List<IkePayload> outboundPayloads) {
+ List<IkeNotifyPayload> notifyPayloads =
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, outboundPayloads);
+ assertEquals(1, notifyPayloads.size());
+ IkeNotifyPayload notifyPayload = notifyPayloads.get(0);
+ assertEquals(NOTIFY_TYPE_REKEY_SA, notifyPayload.notifyType);
+ assertEquals(PROTOCOL_ID_ESP, notifyPayload.protocolId);
+ assertEquals(mSpyCurrentChildSaRecord.getLocalSpi(), notifyPayload.spi);
+ }
+
+ @Test
+ public void testRekeyChildLocalCreateSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(false),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+
+ // Verify outbound payload list
+ List<IkePayload> reqPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(reqPayloadList, true /*isRekey*/);
+
+ verifyOutboundRekeySaPayload(reqPayloadList, false /*isResp*/);
+ verifyOutboundRekeyNotifyPayload(reqPayloadList);
+ }
+
+ private List<IkePayload> makeInboundRekeyChildPayloads(
+ int remoteSpi, String inboundSaHexString, boolean isLocalInitRekey) throws Exception {
+ List<IkePayload> inboundPayloads = new LinkedList<>();
+
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ (IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA, true, inboundSaHexString));
+ inboundPayloads.add(saPayload);
+
+ // Build TS Payloads
+ IkeTrafficSelector[] initTs =
+ isLocalInitRekey
+ ? mChildSessionStateMachine.mLocalTs
+ : mChildSessionStateMachine.mRemoteTs;
+ IkeTrafficSelector[] respTs =
+ isLocalInitRekey
+ ? mChildSessionStateMachine.mRemoteTs
+ : mChildSessionStateMachine.mLocalTs;
+ inboundPayloads.add(new IkeTsPayload(true /*isInitiator*/, initTs));
+ inboundPayloads.add(new IkeTsPayload(false /*isInitiator*/, respTs));
+
+ // Build Nonce Payloads
+ inboundPayloads.add(new IkeNoncePayload());
+
+ if (isLocalInitRekey) {
+ // Rekey-Create response without Notify-Rekey payload is valid.
+ return inboundPayloads;
+ }
+
+ // Build Rekey-Notification
+ inboundPayloads.add(
+ new IkeNotifyPayload(
+ PROTOCOL_ID_ESP,
+ mSpyCurrentChildSaRecord.getRemoteSpi(),
+ NOTIFY_TYPE_REKEY_SA,
+ new byte[0]));
+
+ return inboundPayloads;
+ }
+
+ @Test
+ public void testRekeyChildLocalCreateValidatesResponse() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+
+ // Prepare "rekeyed" SA and receive Rekey response
+ List<IkePayload> rekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ when(mMockSaRecordHelper.makeChildSaRecord(
+ any(List.class), eq(rekeyRespPayloads), any(ChildSaRecordConfig.class)))
+ .thenReturn(mSpyLocalInitNewChildSaRecord);
+
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify state transition
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalDelete);
+
+ // Verify newly created ChildSaRecord
+ assertEquals(
+ mSpyLocalInitNewChildSaRecord,
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ eq(mSpyLocalInitNewChildSaRecord.getRemoteSpi()),
+ eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ any(List.class),
+ eq(rekeyRespPayloads),
+ mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ LOCAL_INIT_NEW_CHILD_SA_SPI_IN,
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ true /*isLocalInit*/);
+
+ // Verify users have been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, true /*expectInbound*/);
+ verifyNotifyUsersCreateIpSecSa(mSpyLocalInitNewChildSaRecord, false /*expectInbound*/);
+ }
+
+ @Test
+ public void testRekeyLocalCreateHandlesErrorNotifyResp() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+
+ // Receive error notification in Create response
+ IkeNotifyPayload notifyPayload = new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ respPayloads.add(notifyPayload);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify rekey has been rescheduled and Child Session is alive
+ verify(mMockChildSessionSmCallback)
+ .scheduleRetryLocalRequest(
+ (ChildLocalRequest) mSpyCurrentChildSaRecord.getFutureRekeyEvent());
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyLocalCreateHandlesRespWithMissingPayload() throws Exception {
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ reset(mMockChildSessionSmCallback);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+
+ // Receive response with no SA Payload
+ List<IkePayload> validRekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ List<IkePayload> respPayloads = new LinkedList<>();
+ for (IkePayload payload : validRekeyRespPayloads) {
+ if (IkePayload.PAYLOAD_TYPE_SA == payload.payloadType) continue;
+ respPayloads.add(payload);
+ }
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, respPayloads);
+ mLooper.dispatchAll();
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(InvalidSyntaxException.class);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // Verify no SPI for provisional Child was registered.
+ verify(mMockChildSessionSmCallback, never())
+ .onChildSaCreated(anyInt(), eq(mChildSessionStateMachine));
+
+ // Verify retry was not scheduled
+ verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any());
+ }
+
+ @Test
+ public void testRekeyLocalCreateChildHandlesKeyCalculationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException(
+ "testRekeyCreateChildHandlesKeyCalculationFail"));
+
+ // Setup for rekey negotiation
+ setupIdleStateMachine();
+ setUpSpiResource(LOCAL_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+ reset(mMockChildSessionSmCallback);
+
+ // Send Rekey-Create request
+ mChildSessionStateMachine.rekeyChildSession();
+ mLooper.dispatchAll();
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalCreate);
+
+ // Receive Rekey response
+ List<IkePayload> rekeyRespPayloads =
+ makeInboundRekeyChildPayloads(
+ LOCAL_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_RESP_SA_PAYLOAD,
+ true /*isLocalInitRekey*/);
+ mChildSessionStateMachine.receiveResponse(EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyRespPayloads);
+ mLooper.dispatchAll();
+
+ // Verify user was notified and state machine has quit.
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // Verify SPI for provisional Child was registered and unregistered.
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT, mChildSessionStateMachine);
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(LOCAL_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ // Verify retry was not scheduled
+ verify(mMockChildSessionSmCallback, never()).scheduleRetryLocalRequest(any());
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Verify outbound delete request
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildLocalDelete);
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), false /*isResp*/);
+
+ assertEquals(mSpyCurrentChildSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+ assertEquals(
+ mSpyLocalInitNewChildSaRecord, mChildSessionStateMachine.mChildSaRecordSurviving);
+ }
+
+ void verifyChildSaUpdated(ChildSaRecord oldSaRecord, ChildSaRecord newSaRecord) {
+ verify(mMockChildSessionSmCallback).onChildSaDeleted(oldSaRecord.getRemoteSpi());
+ verify(oldSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.mChildSaRecordSurviving);
+ assertEquals(newSaRecord, mChildSessionStateMachine.mCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteValidatesResponse() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete response
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ // First invoked in #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord);
+
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockChildSessionCallback, never()).onClosed();
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete response with missing Delete payload
+ mChildSessionStateMachine.receiveResponse(
+ EXCHANGE_TYPE_INFORMATIONAL, new ArrayList<IkePayload>());
+ mLooper.dispatchAll();
+
+ // Verify rekey has finished
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyLocalInitNewChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+
+ // First invoked in #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreate() throws Exception {
+ setupIdleStateMachine();
+
+ // Setup for new Child SA negotiation.
+ setUpSpiResource(LOCAL_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_IN);
+ setUpSpiResource(REMOTE_ADDRESS, REMOTE_INIT_NEW_CHILD_SA_SPI_OUT);
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+ when(mMockSaRecordHelper.makeChildSaRecord(
+ eq(rekeyReqPayloads), any(List.class), any(ChildSaRecordConfig.class)))
+ .thenReturn(mSpyRemoteInitNewChildSaRecord);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
+
+ // Verify outbound rekey response
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+ verifyOutboundCreatePayloadTypes(respPayloadList, true /*isRekey*/);
+
+ verifyOutboundRekeySaPayload(respPayloadList, true /*isResp*/);
+ verifyOutboundRekeyNotifyPayload(respPayloadList);
+
+ // Verify new Child SA
+ assertEquals(
+ mSpyRemoteInitNewChildSaRecord,
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord);
+
+ verify(mMockChildSessionSmCallback)
+ .onChildSaCreated(
+ eq(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()),
+ eq(mChildSessionStateMachine));
+ verify(mMockChildSessionSmCallback)
+ .scheduleLocalRequest(argThat(mRekeyChildLocalReqMatcher), anyLong());
+
+ verify(mMockSaRecordHelper)
+ .makeChildSaRecord(
+ eq(rekeyReqPayloads),
+ any(List.class),
+ mChildSaRecordConfigCaptor.capture());
+ ChildSaRecordConfig childSaRecordConfig = mChildSaRecordConfigCaptor.getValue();
+ verifyChildSaRecordConfig(
+ childSaRecordConfig,
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REMOTE_INIT_NEW_CHILD_SA_SPI_IN,
+ false /*isLocalInit*/);
+
+ // Verify that users are notified the creation of new inbound IpSecTransform
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, true /*expectInbound*/);
+ }
+
+ private void verifyOutboundErrorNotify(int exchangeType, int errorCode) {
+ verify(mMockChildSessionSmCallback)
+ .onOutboundPayloadsReady(
+ eq(exchangeType),
+ eq(true),
+ mPayloadListCaptor.capture(),
+ eq(mChildSessionStateMachine));
+ List<IkePayload> respPayloadList = mPayloadListCaptor.getValue();
+
+ assertEquals(1, respPayloadList.size());
+ IkePayload payload = respPayloadList.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(errorCode, ((IkeNotifyPayload) payload).notifyType);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreateHandlesInvalidReq() throws Exception {
+ setupIdleStateMachine();
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_UNACCEPTABLE_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent and state machind was back to Idle
+ verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyChildRemoteCreateSaCreationFail() throws Exception {
+ // Throw exception when building ChildSaRecord
+ when(mMockSaRecordHelper.makeChildSaRecord(any(), any(), any()))
+ .thenThrow(
+ new GeneralSecurityException("testRekeyChildRemoteCreateSaCreationFail"));
+
+ setupIdleStateMachine();
+
+ List<IkePayload> rekeyReqPayloads =
+ makeInboundRekeyChildPayloads(
+ REMOTE_INIT_NEW_CHILD_SA_SPI_OUT,
+ REKEY_CHILD_REQ_SA_PAYLOAD,
+ false /*isLocalInitRekey*/);
+
+ // Receive rekey Child request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_REKEY_CHILD, EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyReqPayloads);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent and state machind was back to Idle
+ verifyOutboundErrorNotify(EXCHANGE_TYPE_CREATE_CHILD_SA, ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyChildRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+
+ // Test receiving Delete request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyCurrentChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response
+ verifyOutboundDeletePayload(mSpyCurrentChildSaRecord.getLocalSpi(), true /*isResp*/);
+
+ // Verify Child SA has been updated
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord);
+
+ // Verify procedure has been finished. #onProcedureFinished was first invoked in
+ // #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildLocalDeleteWithReqForNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete new Child SA request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyLocalInitNewChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response on new Child SA
+ verifyOutboundDeletePayload(mSpyLocalInitNewChildSaRecord.getLocalSpi(), true /*isResp*/);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaDeleted(mSpyLocalInitNewChildSaRecord.getRemoteSpi());
+ verify(mSpyLocalInitNewChildSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteWithReqForNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Test receiving Delete new Child SA request
+ mChildSessionStateMachine.receiveRequest(
+ IKE_EXCHANGE_SUBTYPE_DELETE_CHILD,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ makeDeletePayloads(mSpyRemoteInitNewChildSaRecord.getRemoteSpi()));
+ mLooper.dispatchAll();
+
+ // Verify outbound Delete response on new Child SA
+ verifyOutboundDeletePayload(mSpyRemoteInitNewChildSaRecord.getLocalSpi(), true /*isResp*/);
+ verify(mMockChildSessionSmCallback)
+ .onChildSaDeleted(mSpyRemoteInitNewChildSaRecord.getRemoteSpi());
+ verify(mSpyRemoteInitNewChildSaRecord).close();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyRemoteInitNewChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteTimeout() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Verify no response sent.
+ verify(mMockChildSessionSmCallback, never())
+ .onOutboundPayloadsReady(anyInt(), anyBoolean(), any(List.class), anyObject());
+
+ // Verify Child SA has been renewed
+ verifyChildSaUpdated(mSpyCurrentChildSaRecord, mSpyRemoteInitNewChildSaRecord);
+
+ // Verify procedure has been finished. #onProcedureFinished was first invoked in
+ // #setupIdleStateMachine
+ verify(mMockChildSessionSmCallback, times(2))
+ .onProcedureFinished(mChildSessionStateMachine);
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.Idle);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testRekeyChildRemoteDeleteExitAndRenter() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildRemoteDelete
+ mChildSessionStateMachine.mRemoteInitNewChildSaRecord = mSpyRemoteInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Trigger a timeout, and immediately re-enter remote-delete
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mChildSessionStateMachine.sendMessage(ChildSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE);
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Shift time forward
+ mLooper.moveTimeForward(REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mLooper.dispatchAll();
+
+ // Verify final state has not changed - timeout was not triggered.
+ assertTrue(
+ mChildSessionStateMachine.getCurrentState()
+ instanceof ChildSessionStateMachine.RekeyChildRemoteDelete);
+
+ verify(mSpyUserCbExecutor, times(2)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUsersCreateIpSecSa(mSpyRemoteInitNewChildSaRecord, false /*expectInbound*/);
+
+ verify(mMockChildSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testCloseSessionNow() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyChildLocalDelete
+ mChildSessionStateMachine.mLocalInitNewChildSaRecord = mSpyLocalInitNewChildSaRecord;
+ mChildSessionStateMachine.sendMessage(
+ CMD_FORCE_TRANSITION, mChildSessionStateMachine.mRekeyChildLocalDelete);
+
+ mChildSessionStateMachine.killSession();
+ mLooper.dispatchAll();
+
+ assertNull(mChildSessionStateMachine.getCurrentState());
+
+ verify(mSpyUserCbExecutor, times(3)).execute(any(Runnable.class));
+
+ verifyNotifyUserDeleteChildSa(mSpyCurrentChildSaRecord);
+ verifyNotifyUserDeleteChildSa(mSpyLocalInitNewChildSaRecord);
+
+ verify(mMockChildSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testValidateExpectKeExistCase() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP));
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testValidateExpectNoKeExistCase() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms()).thenReturn(new DhGroupTransform[0]);
+ List<IkePayload> payloadList = new LinkedList<>();
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testThrowWhenKeMissing() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to the absence of KE Payload");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to the absence of KE Payload");
+ } catch (InvalidKeException expected) {
+ }
+ }
+
+ @Test
+ public void testThrowWhenKeHasMismatchedDhGroup() throws Exception {
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {mChildDhGroupTransform});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP));
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to mismatched DH Group");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to mismatched DH Group");
+ } catch (InvalidKeException expected) {
+ }
+ }
+
+ @Test
+ public void testThrowForUnexpectedKe() throws Exception {
+ DhGroupTransform noneGroup = new DhGroupTransform(SaProposal.DH_GROUP_NONE);
+ when(mMockNegotiatedProposal.getDhGroupTransforms())
+ .thenReturn(new DhGroupTransform[] {noneGroup});
+ List<IkePayload> payloadList = new LinkedList<>();
+ payloadList.add(new IkeKePayload(SaProposal.DH_GROUP_2048_BIT_MODP));
+
+ try {
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, true /*isResp*/, mMockNegotiatedProposal);
+ fail("Expected to fail due to unexpected KE payload.");
+ } catch (InvalidSyntaxException expected) {
+ }
+
+ CreateChildSaHelper.validateKePayloads(
+ payloadList, false /*isResp*/, mMockNegotiatedProposal);
+ }
+
+ @Test
+ public void testHandleUnexpectedException() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ mChildSessionStateMachine.createChildSession(
+ null /*localAddress*/, REMOTE_ADDRESS, mMockUdpEncapSocket, mIkePrf, SK_D);
+ mLooper.dispatchAll();
+
+ verifyHandleFatalErrorAndQuit(IkeInternalException.class);
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java
new file mode 100644
index 00000000..3406d014
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeLocalRequestSchedulerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.IProcedureConsumer;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+public final class IkeLocalRequestSchedulerTest {
+ private IkeLocalRequestScheduler mScheduler;
+
+ private IProcedureConsumer mMockConsumer;
+ private LocalRequest[] mMockRequestArray;
+
+ private ArgumentCaptor<LocalRequest> mLocalRequestCaptor =
+ ArgumentCaptor.forClass(LocalRequest.class);
+
+ @Before
+ public void setUp() {
+ mMockConsumer = mock(IProcedureConsumer.class);
+ mScheduler = new IkeLocalRequestScheduler(mMockConsumer);
+
+ mMockRequestArray = new LocalRequest[10];
+ for (int i = 0; i < mMockRequestArray.length; i++) {
+ mMockRequestArray[i] = mock(LocalRequest.class);
+ }
+ }
+
+ @Test
+ public void testAddMultipleRequestProcessOnlyOne() {
+ for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r);
+
+ // Verify that no procedure was preemptively pulled from the queue
+ verify(mMockConsumer, never()).onNewProcedureReady(any());
+
+ // Check that the onNewPrcedureReady called exactly once, on the first item
+ mScheduler.readyForNextProcedure();
+ verify(mMockConsumer, times(1)).onNewProcedureReady(any());
+ verify(mMockConsumer, times(1)).onNewProcedureReady(mMockRequestArray[0]);
+ for (int i = 1; i < mMockRequestArray.length; i++) {
+ verify(mMockConsumer, never()).onNewProcedureReady(mMockRequestArray[i]);
+ }
+ }
+
+ @Test
+ public void testProcessOrder() {
+ InOrder inOrder = inOrder(mMockConsumer);
+
+ for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r);
+ for (int i = 0; i < mMockRequestArray.length; i++) mScheduler.readyForNextProcedure();
+
+ for (LocalRequest r : mMockRequestArray) {
+ inOrder.verify(mMockConsumer).onNewProcedureReady(r);
+ }
+ }
+
+ @Test
+ public void testAddRequestToFrontProcessOrder() {
+ InOrder inOrder = inOrder(mMockConsumer);
+
+ LocalRequest[] mockHighPriorityRequestArray = new LocalRequest[10];
+ for (int i = 0; i < mockHighPriorityRequestArray.length; i++) {
+ mockHighPriorityRequestArray[i] = mock(LocalRequest.class);
+ }
+
+ for (LocalRequest r : mMockRequestArray) mScheduler.addRequest(r);
+ for (LocalRequest r : mockHighPriorityRequestArray) mScheduler.addRequestAtFront(r);
+
+ for (int i = 0; i < mockHighPriorityRequestArray.length + mMockRequestArray.length; i++) {
+ mScheduler.readyForNextProcedure();
+ }
+
+ // Verify processing order. mockHighPriorityRequestArray is processed in reverse order
+ for (int i = mockHighPriorityRequestArray.length - 1; i >= 0; i--) {
+ inOrder.verify(mMockConsumer).onNewProcedureReady(mockHighPriorityRequestArray[i]);
+ }
+ for (LocalRequest r : mMockRequestArray) {
+ inOrder.verify(mMockConsumer).onNewProcedureReady(r);
+ }
+ }
+
+ @Test
+ public void testDoNotProcessCanceledRequest() {
+ LocalRequest[] requestArray = new LocalRequest[4];
+
+ for (int i = 0; i < requestArray.length; i++) {
+ requestArray[i] = new LocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE);
+ mScheduler.addRequest(requestArray[i]);
+ }
+
+ mScheduler.readyForNextProcedure();
+ verify(mMockConsumer).onNewProcedureReady(eq(requestArray[0]));
+
+ requestArray[1].cancel();
+ mScheduler.readyForNextProcedure();
+ verify(mMockConsumer, never()).onNewProcedureReady(eq(requestArray[1]));
+ verify(mMockConsumer).onNewProcedureReady(eq(requestArray[2]));
+
+ mScheduler.readyForNextProcedure();
+ verify(mMockConsumer).onNewProcedureReady(eq(requestArray[3]));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
new file mode 100644
index 00000000..94f4d622
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java
@@ -0,0 +1,4036 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
+
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.RETRY_INTERVAL_MS;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.SA_SOFT_LIFETIME_MS;
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
+import static com.android.internal.net.ipsec.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
+import static com.android.internal.net.ipsec.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_SA;
+
+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 static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.eap.EapSessionConfig;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionOptions;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeManager;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionOptions;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.TransportModeChildSessionOptions;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.eap.EapAuthenticator;
+import com.android.internal.net.eap.IEapCallback;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachine.IChildSessionSmCallback;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.ChildSessionFactoryHelper;
+import com.android.internal.net.ipsec.ike.ChildSessionStateMachineFactory.IChildSessionFactoryHelper;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
+import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.ReceivedIkePacket;
+import com.android.internal.net.ipsec.ike.SaRecord.ISaRecordHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecordConfig;
+import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
+import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException;
+import com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload;
+import com.android.internal.net.ipsec.ike.message.IkeAuthPayload;
+import com.android.internal.net.ipsec.ike.message.IkeAuthPskPayload;
+import com.android.internal.net.ipsec.ike.message.IkeCertX509CertPayload;
+import com.android.internal.net.ipsec.ike.message.IkeDeletePayload;
+import com.android.internal.net.ipsec.ike.message.IkeEapPayload;
+import com.android.internal.net.ipsec.ike.message.IkeHeader;
+import com.android.internal.net.ipsec.ike.message.IkeIdPayload;
+import com.android.internal.net.ipsec.ike.message.IkeInformationalPayload;
+import com.android.internal.net.ipsec.ike.message.IkeKePayload;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultOk;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultProtectedError;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultUnprotectedError;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.IIkeMessageHelper;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.IkeMessageHelper;
+import com.android.internal.net.ipsec.ike.message.IkeNoncePayload;
+import com.android.internal.net.ipsec.ike.message.IkeNotifyPayload;
+import com.android.internal.net.ipsec.ike.message.IkePayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSkfPayload;
+import com.android.internal.net.ipsec.ike.message.IkeTestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeTsPayload;
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.internal.net.ipsec.ike.utils.Retransmitter;
+import com.android.internal.net.ipsec.ike.utils.Retransmitter.IBackoffTimeoutCalculator;
+import com.android.internal.net.utils.Log;
+import com.android.internal.util.State;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public final class IkeSessionStateMachineTest {
+ private static final String TAG = "IkeSessionStateMachineTest";
+
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("127.0.0.1"));
+
+ private static final String IKE_INIT_RESP_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c21202220000000000000014c220000300000"
+ + "002c010100040300000c0100000c800e008003000008030000020300000802000002"
+ + "00000008040000022800008800020000fe014fefed55a4229928bfa3dad1ea6ffaca"
+ + "abfb5f5bdd71790e99a192530e3f849d3a3d96dc6e0a7a10ff6f72a6162103ac573c"
+ + "acd41d08b7a034cad8f5eab09c14ced5a9e4af5692dff028f21c1119dd75226b6af6"
+ + "b2f009245369c9892cc5742e5c94a254ebff052470771fb2cb4f29a35d8953e18a1a"
+ + "6c6fbc56acc188a5290000249756112ca539f5c25abacc7ee92b73091942a9c06950"
+ + "f98848f1af1694c4ddff2900001c00004004c53f054b976a25d75fde72dbf1c7b6c8"
+ + "c9aa9ca12900001c00004005b16d79b21c1bc89ca7350f42de805be0227e2ed62b00"
+ + "00080000401400000014882fe56d6fd20dbc2251613b2ebe5beb";
+ private static final String IKE_SA_PAYLOAD_HEX_STRING =
+ "220000300000002c010100040300000c0100000c800e00800300000803000002030"
+ + "00008020000020000000804000002";
+ private static final String IKE_REKEY_SA_PAYLOAD_HEX_STRING =
+ "22000038000000340101080400000000000000FF0300000c0100000c800e0080030"
+ + "000080300000203000008020000020000000804000002";
+ private static final String IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING =
+ "22000038000000340101080400000000000000FF0300000c0100000c800e0080030"
+ + "00008030000020300000802000002000000080400000e";
+ private static final int IKE_REKEY_SA_INITIATOR_SPI = 0xff;
+ private static final String KE_PAYLOAD_HEX_STRING =
+ "2800008800020000b4a2faf4bb54878ae21d638512ece55d9236fc50"
+ + "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa"
+ + "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd"
+ + "92f46bef84f0be7db860351843858f8acf87056e272377f7"
+ + "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ + "6bbeb08214c7071376079587";
+ private static final String NONCE_INIT_PAYLOAD_HEX_STRING =
+ "29000024c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+ private static final String NONCE_RESP_PAYLOAD_HEX_STRING =
+ "290000249756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String NONCE_INIT_HEX_STRING =
+ "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+ private static final String NONCE_RESP_HEX_STRING =
+ "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING =
+ "2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0";
+ private static final String NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING =
+ "2b00001c00004005d915368ca036004cb578ae3e3fb268509aeab190";
+ private static final String FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING = "290000080000402e";
+ private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
+ private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009";
+ private static final String ID_PAYLOAD_INITIATOR_HEX_STRING =
+ "290000180200000031313233343536373839414243444546";
+ private static final String ID_PAYLOAD_RESPONDER_HEX_STRING = "2700000c010000007f000001";
+ private static final String PSK_AUTH_RESP_PAYLOAD_HEX_STRING =
+ "2100001c0200000058f36412e9b7b38df817a9f7779b7a008dacdd25";
+ private static final String GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING =
+ "300000580e0000000f300d06092a864886f70d01010b05006f76af4150d653c5d413"
+ + "6b9f69d905849bf075c563e6d14ccda42361ec3e7d12c72e2dece5711ea1d952f7b8e"
+ + "12c5d982aa4efdaeac36a02b222aa96242cc424";
+ private static final String CHILD_SA_PAYLOAD_HEX_STRING =
+ "2c00002c0000002801030403cae7019f0300000c0100000c800e008003000008030"
+ + "000020000000805000000";
+ private static final String TS_INIT_PAYLOAD_HEX_STRING =
+ "2d00001801000000070000100000ffff00000000ffffffff";
+ private static final String TS_RESP_PAYLOAD_HEX_STRING =
+ "2900001801000000070000100000ffff000000000fffffff";
+
+ private static final String PSK_HEX_STRING = "6A756E69706572313233";
+
+ private static final String PRF_KEY_INIT_HEX_STRING =
+ "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String PRF_KEY_RESP_HEX_STRING =
+ "A30E6B08BE56C0E6BFF4744143C75219299E1BEB";
+
+ private static final byte[] EAP_DUMMY_MSG = "EAP Message".getBytes();
+
+ private static final int KEY_LEN_IKE_INTE = 20;
+ private static final int KEY_LEN_IKE_ENCR = 16;
+ private static final int KEY_LEN_IKE_PRF = 20;
+ private static final int KEY_LEN_IKE_SKD = KEY_LEN_IKE_PRF;
+
+ private static final int CHILD_SPI_LOCAL = 0x2ad4c0a2;
+ private static final int CHILD_SPI_REMOTE = 0xcae7019f;
+
+ private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234;
+ private static final int UDP_ENCAP_PORT = 34567;
+
+ private static final int EAP_SIM_SUB_ID = 1;
+
+ private static final int PAYLOAD_TYPE_UNSUPPORTED = 127;
+
+ private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L;
+
+ private MockIpSecTestUtils mMockIpSecTestUtils;
+ private Context mContext;
+ private IpSecManager mIpSecManager;
+ private UdpEncapsulationSocket mUdpEncapSocket;
+
+ private IkeSocket mSpyIkeSocket;
+
+ private TestLooper mLooper;
+ private IkeSessionStateMachine mIkeSessionStateMachine;
+
+ private byte[] mPsk;
+
+ private ChildSessionOptions mChildSessionOptions;
+
+ private Executor mSpyUserCbExecutor;
+ private IkeSessionCallback mMockIkeSessionCallback;
+ private ChildSessionCallback mMockChildSessionCallback;
+
+ private EncryptionTransform mIkeEncryptionTransform;
+ private IntegrityTransform mIkeIntegrityTransform;
+ private PrfTransform mIkePrfTransform;
+ private DhGroupTransform mIkeDhGroupTransform;
+
+ private IIkeMessageHelper mMockIkeMessageHelper;
+ private ISaRecordHelper mMockSaRecordHelper;
+ private IBackoffTimeoutCalculator mMockBackoffTimeoutCalculator;
+
+ private ChildSessionStateMachine mMockChildSessionStateMachine;
+ private IChildSessionFactoryHelper mMockChildSessionFactoryHelper;
+ private IChildSessionSmCallback mDummyChildSmCallback;
+
+ private IkeSaRecord mSpyCurrentIkeSaRecord;
+ private IkeSaRecord mSpyLocalInitIkeSaRecord;
+ private IkeSaRecord mSpyRemoteInitIkeSaRecord;
+
+ private Log mSpyIkeLog;
+
+ private int mExpectedCurrentSaLocalReqMsgId;
+ private int mExpectedCurrentSaRemoteReqMsgId;
+
+ private EapSessionConfig mEapSessionConfig;
+ private IkeEapAuthenticatorFactory mMockEapAuthenticatorFactory;
+ private EapAuthenticator mMockEapAuthenticator;
+
+ private X509Certificate mRootCertificate;
+ private X509Certificate mServerEndCertificate;
+
+ private ArgumentCaptor<IkeMessage> mIkeMessageCaptor =
+ ArgumentCaptor.forClass(IkeMessage.class);
+ private ArgumentCaptor<IkeSaRecordConfig> mIkeSaRecordConfigCaptor =
+ ArgumentCaptor.forClass(IkeSaRecordConfig.class);
+ private ArgumentCaptor<IChildSessionSmCallback> mChildSessionSmCbCaptor =
+ ArgumentCaptor.forClass(IChildSessionSmCallback.class);
+ private ArgumentCaptor<List<IkePayload>> mPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+
+ private ReceivedIkePacket makeDummyReceivedIkeInitRespPacket(
+ long initiatorSpi,
+ long responderSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromIkeInit,
+ List<Integer> payloadTypeList,
+ List<String> payloadHexStringList)
+ throws Exception {
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ // Build a remotely generated NAT_DETECTION_SOURCE_IP payload to mock a remote node's
+ // network that is not behind NAT.
+ IkePayload sourceNatPayload =
+ new IkeNotifyPayload(
+ NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP,
+ IkeNotifyPayload.generateNatDetectionData(
+ initiatorSpi,
+ responderSpi,
+ REMOTE_ADDRESS,
+ IkeSocket.IKE_SERVER_PORT));
+ payloadList.add(sourceNatPayload);
+ return makeDummyUnencryptedReceivedIkePacket(
+ initiatorSpi, responderSpi, eType, isResp, fromIkeInit, payloadList);
+ }
+
+ private ReceivedIkePacket makeDummyUnencryptedReceivedIkePacket(
+ long initiatorSpi,
+ long responderSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromIkeInit,
+ List<IkePayload> payloadList)
+ throws Exception {
+ IkeMessage dummyIkeMessage =
+ makeDummyIkeMessageForTest(
+ initiatorSpi,
+ responderSpi,
+ eType,
+ isResp,
+ fromIkeInit,
+ 0,
+ false /*isEncrypted*/,
+ payloadList);
+
+ byte[] dummyIkePacketBytes = new byte[0];
+ when(mMockIkeMessageHelper.decode(0, dummyIkeMessage.ikeHeader, dummyIkePacketBytes))
+ .thenReturn(new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacket(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ List<Integer> payloadTypeList,
+ List<String> payloadHexStringList)
+ throws Exception {
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ ikeSaRecord, eType, isResp, payloadList);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ List<IkePayload> payloadList)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ ikeSaRecord,
+ eType,
+ isResp,
+ isResp
+ ? ikeSaRecord.getLocalRequestMessageId()
+ : ikeSaRecord.getRemoteRequestMessageId(),
+ payloadList,
+ new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private ReceivedIkePacket makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ IkeSaRecord ikeSaRecord,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ int msgId,
+ List<IkePayload> payloadList,
+ byte[] dummyIkePacketBytes)
+ throws Exception {
+ boolean fromIkeInit = !ikeSaRecord.isLocalInit;
+ IkeMessage dummyIkeMessage =
+ makeDummyIkeMessageForTest(
+ ikeSaRecord.getInitiatorSpi(),
+ ikeSaRecord.getResponderSpi(),
+ eType,
+ isResp,
+ fromIkeInit,
+ msgId,
+ true /*isEncyprted*/,
+ payloadList);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ dummyIkeMessage.ikeHeader,
+ null /*collectedFrags*/,
+ new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithInvalidSyntax(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType) {
+ return makeDummyReceivedIkePacketWithDecodingError(
+ ikeSaRecord, isResp, eType, new InvalidSyntaxException("IkeStateMachineTest"));
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithDecodingError(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeProtocolException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK);
+ byte[] dummyPacket = new byte[0];
+ when(mMockIkeMessageHelper.decode(
+ anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+ .thenReturn(new DecodeResultProtectedError(exception, dummyPacket));
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkePacketWithUnprotectedError(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, IkeException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SK);
+ byte[] dummyPacket = new byte[0];
+ when(mMockIkeMessageHelper.decode(
+ anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any()))
+ .thenReturn(new DecodeResultUnprotectedError(exception));
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedIkeFragmentPacket(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ IkeSkfPayload skfPayload,
+ int nextPayloadType,
+ DecodeResultPartial collectedFrags) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ byte[] dummyPacket = new byte[0];
+ DecodeResultPartial resultFrags =
+ new DecodeResultPartial(
+ header, dummyPacket, skfPayload, nextPayloadType, collectedFrags);
+ setDecodeEncryptedPacketResult(ikeSaRecord, header, collectedFrags, resultFrags);
+
+ return new ReceivedIkePacket(header, dummyPacket);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketOk(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ List<IkePayload> payloadList,
+ byte[] firstFragBytes) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ IkeMessage completeMessage = new IkeMessage(header, payloadList);
+
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ header,
+ collectedFrags,
+ new DecodeResultOk(completeMessage, firstFragBytes));
+
+ return new ReceivedIkePacket(header, new byte[0] /*dummyIkePacketBytes*/);
+ }
+
+ private ReceivedIkePacket makeDummyReceivedLastIkeFragmentPacketError(
+ IkeSaRecord ikeSaRecord,
+ boolean isResp,
+ int eType,
+ DecodeResultPartial collectedFrags,
+ IkeException exception) {
+ IkeHeader header =
+ makeDummyIkeHeader(ikeSaRecord, isResp, eType, IkePayload.PAYLOAD_TYPE_SKF);
+
+ byte[] dummyIkePacketBytes = new byte[0];
+ setDecodeEncryptedPacketResult(
+ ikeSaRecord,
+ header,
+ collectedFrags,
+ new DecodeResultProtectedError(exception, dummyIkePacketBytes));
+
+ return new ReceivedIkePacket(header, dummyIkePacketBytes);
+ }
+
+ private IkeHeader makeDummyIkeHeader(
+ IkeSaRecord ikeSaRecord, boolean isResp, int eType, int firstPayloadType) {
+ return new IkeHeader(
+ ikeSaRecord.getInitiatorSpi(),
+ ikeSaRecord.getResponderSpi(),
+ firstPayloadType,
+ eType,
+ isResp,
+ !ikeSaRecord.isLocalInit,
+ isResp
+ ? ikeSaRecord.getLocalRequestMessageId()
+ : ikeSaRecord.getRemoteRequestMessageId());
+ }
+
+ private void setDecodeEncryptedPacketResult(
+ IkeSaRecord ikeSaRecord,
+ IkeHeader header,
+ DecodeResultPartial collectedFrags,
+ DecodeResult result) {
+ when(mMockIkeMessageHelper.decode(
+ anyInt(),
+ any(),
+ any(),
+ eq(ikeSaRecord),
+ eq(header),
+ any(),
+ eq(collectedFrags)))
+ .thenReturn(result);
+ }
+
+ private IkeMessage makeDummyIkeMessageForTest(
+ long initSpi,
+ long respSpi,
+ @IkeHeader.ExchangeType int eType,
+ boolean isResp,
+ boolean fromikeInit,
+ int messageId,
+ boolean isEncrypted,
+ List<IkePayload> payloadList)
+ throws Exception {
+ int firstPayloadType =
+ isEncrypted ? IkePayload.PAYLOAD_TYPE_SK : IkePayload.PAYLOAD_TYPE_NO_NEXT;
+
+ IkeHeader header =
+ new IkeHeader(
+ initSpi, respSpi, firstPayloadType, eType, isResp, fromikeInit, messageId);
+
+ return new IkeMessage(header, payloadList);
+ }
+
+ private static List<IkePayload> hexStrListToIkePayloadList(
+ List<Integer> payloadTypeList, List<String> payloadHexStringList, boolean isResp)
+ throws Exception {
+ List<IkePayload> payloadList = new LinkedList<>();
+ for (int i = 0; i < payloadTypeList.size(); i++) {
+ payloadList.add(
+ IkeTestUtils.hexStringToIkePayload(
+ payloadTypeList.get(i), isResp, payloadHexStringList.get(i)));
+ }
+ return payloadList;
+ }
+
+ private void verifyDecodeEncryptedMessage(IkeSaRecord record, ReceivedIkePacket rcvPacket)
+ throws Exception {
+ verify(mMockIkeMessageHelper)
+ .decode(
+ anyInt(),
+ any(),
+ any(),
+ eq(record),
+ eq(rcvPacket.ikeHeader),
+ eq(rcvPacket.ikePacketBytes),
+ eq(null));
+ }
+
+ private static IkeSaRecord makeDummyIkeSaRecord(long initSpi, long respSpi, boolean isLocalInit)
+ throws IOException {
+ Inet4Address initAddress = isLocalInit ? LOCAL_ADDRESS : REMOTE_ADDRESS;
+ Inet4Address respAddress = isLocalInit ? REMOTE_ADDRESS : LOCAL_ADDRESS;
+
+ return new IkeSaRecord(
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(initAddress, initSpi),
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(respAddress, respSpi),
+ isLocalInit,
+ TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING),
+ TestUtils.hexStringToByteArray(NONCE_RESP_HEX_STRING),
+ new byte[KEY_LEN_IKE_SKD],
+ new byte[KEY_LEN_IKE_INTE],
+ new byte[KEY_LEN_IKE_INTE],
+ new byte[KEY_LEN_IKE_ENCR],
+ new byte[KEY_LEN_IKE_ENCR],
+ TestUtils.hexStringToByteArray(PRF_KEY_INIT_HEX_STRING),
+ TestUtils.hexStringToByteArray(PRF_KEY_RESP_HEX_STRING),
+ new LocalRequest(CMD_LOCAL_REQUEST_REKEY_IKE));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG);
+ IkeManager.setIkeLog(mSpyIkeLog);
+
+ mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec();
+ mIpSecManager = mMockIpSecTestUtils.getIpSecManager();
+ mContext = mMockIpSecTestUtils.getContext();
+ mUdpEncapSocket = mIpSecManager.openUdpEncapsulationSocket();
+ mEapSessionConfig =
+ new EapSessionConfig.Builder()
+ .setEapSimConfig(EAP_SIM_SUB_ID, TelephonyManager.APPTYPE_USIM)
+ .build();
+
+ mMockEapAuthenticatorFactory = mock(IkeEapAuthenticatorFactory.class);
+ mMockEapAuthenticator = mock(EapAuthenticator.class);
+ when(mMockEapAuthenticatorFactory.newEapAuthenticator(any(), any(), any(), any()))
+ .thenReturn(mMockEapAuthenticator);
+
+ mRootCertificate = CertUtils.createCertFromPemFile("self-signed-ca-a.pem");
+ mServerEndCertificate = CertUtils.createCertFromPemFile("end-cert-a.pem");
+
+ mPsk = TestUtils.hexStringToByteArray(PSK_HEX_STRING);
+
+ mChildSessionOptions = buildChildSessionOptions();
+
+ mIkeEncryptionTransform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128);
+ mIkeIntegrityTransform =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+ mIkePrfTransform = new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ mIkeDhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP);
+
+ mSpyUserCbExecutor =
+ spy(
+ (command) -> {
+ command.run();
+ });
+
+ mMockIkeSessionCallback = mock(IkeSessionCallback.class);
+ mMockChildSessionCallback = mock(ChildSessionCallback.class);
+
+ mLooper = new TestLooper();
+
+ mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class);
+ mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class);
+ ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
+ mMockChildSessionFactoryHelper);
+ setupChildStateMachineFactory(mMockChildSessionStateMachine);
+
+ // Inject longer retransmission timeout
+ mMockBackoffTimeoutCalculator = mock(IBackoffTimeoutCalculator.class);
+ when(mMockBackoffTimeoutCalculator.getExponentialBackoffTimeout(anyInt()))
+ .thenReturn(RETRANSMIT_BACKOFF_TIMEOUT_MS);
+ Retransmitter.setBackoffTimeoutCalculator(mMockBackoffTimeoutCalculator);
+
+ // Setup state machine
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsPsk(mPsk));
+
+ mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class);
+ IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
+ resetMockIkeMessageHelper();
+
+ mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class);
+ SaRecord.setSaRecordHelper(mMockSaRecordHelper);
+
+ mSpyCurrentIkeSaRecord = spy(makeDummyIkeSaRecord(11, 12, true));
+ mSpyLocalInitIkeSaRecord = spy(makeDummyIkeSaRecord(21, 22, true));
+ mSpyRemoteInitIkeSaRecord = spy(makeDummyIkeSaRecord(31, 32, false));
+
+ mExpectedCurrentSaLocalReqMsgId = 0;
+ mExpectedCurrentSaRemoteReqMsgId = 0;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mIkeSessionStateMachine.quit();
+ mIkeSessionStateMachine.setDbg(false);
+ mUdpEncapSocket.close();
+
+ mSpyCurrentIkeSaRecord.close();
+ mSpyLocalInitIkeSaRecord.close();
+ mSpyRemoteInitIkeSaRecord.close();
+
+ IkeManager.resetIkeLog();
+ Retransmitter.resetBackoffTimeoutCalculator();
+ IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
+ SaRecord.setSaRecordHelper(new SaRecordHelper());
+ ChildSessionStateMachineFactory.setChildSessionFactoryHelper(
+ new ChildSessionFactoryHelper());
+ }
+
+ private IkeSessionStateMachine makeAndStartIkeSession(IkeSessionOptions ikeOptions)
+ throws Exception {
+ IkeSessionStateMachine ikeSession =
+ new IkeSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mIpSecManager,
+ ikeOptions,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockIkeSessionCallback,
+ mMockChildSessionCallback,
+ mMockEapAuthenticatorFactory);
+ ikeSession.setDbg(true);
+
+ mLooper.dispatchAll();
+ ikeSession.mLocalAddress = LOCAL_ADDRESS;
+
+ mSpyIkeSocket = spy(IkeSocket.getIkeSocket(mUdpEncapSocket, ikeSession));
+ doNothing().when(mSpyIkeSocket).sendIkePacket(any(), any());
+ ikeSession.mIkeSocket = mSpyIkeSocket;
+
+ return ikeSession;
+ }
+
+ public static IkeSaProposal buildSaProposal() throws Exception {
+ return new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .build();
+ }
+
+ private IkeSessionOptions.Builder buildIkeSessionOptionsCommon() throws Exception {
+ return new IkeSessionOptions.Builder()
+ .setServerAddress(REMOTE_ADDRESS)
+ .setUdpEncapsulationSocket(mUdpEncapSocket)
+ .addSaProposal(buildSaProposal())
+ .setLocalIdentification(new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS))
+ .setRemoteIdentification(
+ new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS));
+ }
+
+ private IkeSessionOptions buildIkeSessionOptionsPsk(byte[] psk) throws Exception {
+ return buildIkeSessionOptionsCommon().setAuthPsk(psk).build();
+ }
+
+ private IkeSessionOptions buildIkeSessionOptionsEap() throws Exception {
+ return buildIkeSessionOptionsCommon()
+ .setAuthEap(mRootCertificate, mEapSessionConfig)
+ .build();
+ }
+
+ private ChildSessionOptions buildChildSessionOptions() throws Exception {
+ ChildSaProposal saProposal =
+ new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .build();
+
+ return new TransportModeChildSessionOptions.Builder().addSaProposal(saProposal).build();
+ }
+
+ private ReceivedIkePacket makeIkeInitResponse() throws Exception {
+ // TODO: Build real IKE INIT response when IKE INIT response validation is implemented.
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
+
+ payloadHexStringList.add(IKE_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING);
+
+ // In each test assign different IKE responder SPI in IKE INIT response to avoid remote SPI
+ // collision during response validation.
+ // STOPSHIP: b/131617794 allow #mockIkeSetup to be independent in each test after we can
+ // support IkeSession cleanup.
+ return makeDummyReceivedIkeInitRespPacket(
+ 1L /*initiator SPI*/,
+ 2L /*responder SPI*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ false /*fromIkeInit*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private List<IkePayload> getIkeAuthPayloadListWithChildPayloads(
+ List<IkePayload> authRelatedPayloads) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, true /*isResp*/);
+ payloadList.addAll(authRelatedPayloads);
+
+ return payloadList;
+ }
+
+ private ReceivedIkePacket makeIkeAuthRespWithChildPayloads(List<IkePayload> authRelatedPayloads)
+ throws Exception {
+ List<IkePayload> payloadList = getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ payloadList);
+ }
+
+ private ReceivedIkePacket makeIkeAuthRespWithoutChildPayloads(
+ List<IkePayload> authRelatedPayloads) throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ authRelatedPayloads);
+ }
+
+ private ReceivedIkePacket makeCreateChildCreateMessage(boolean isResp) throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ isResp,
+ makeCreateChildPayloadList(isResp));
+ }
+
+ private ReceivedIkePacket makeRekeyChildCreateMessage(boolean isResp, int spi)
+ throws Exception {
+ IkeNotifyPayload rekeyPayload =
+ new IkeNotifyPayload(
+ IkePayload.PROTOCOL_ID_ESP,
+ spi,
+ IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA,
+ new byte[0]);
+
+ List<IkePayload> payloadList = makeCreateChildPayloadList(isResp);
+ payloadList.add(rekeyPayload);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ isResp,
+ payloadList);
+ }
+
+ private List<IkePayload> makeCreateChildPayloadList(boolean isResp) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ return hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, isResp);
+ }
+
+ private ReceivedIkePacket makeDeleteChildPacket(IkeDeletePayload[] payloads, boolean isResp)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ isResp,
+ Arrays.asList(payloads));
+ }
+
+ private ReceivedIkePacket makeRekeyIkeResponse() throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+
+ payloadHexStringList.add(IKE_REKEY_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
+
+ return makeDummyEncryptedReceivedIkePacket(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ true /*isResp*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private ReceivedIkePacket makeDeleteIkeResponse(IkeSaRecord ikeSaRecord) throws Exception {
+ return makeDummyEncryptedReceivedIkePacket(
+ ikeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ true /*isResp*/,
+ new LinkedList<>(),
+ new LinkedList<>());
+ }
+
+ private ReceivedIkePacket makeDpdIkeRequest(IkeSaRecord saRecord) throws Exception {
+ return makeDummyEncryptedReceivedIkePacket(
+ saRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ new LinkedList<>(),
+ new LinkedList<>());
+ }
+
+ private ReceivedIkePacket makeDpdIkeRequest(int msgId, byte[] dummyIkePacketBytes)
+ throws Exception {
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ msgId,
+ new LinkedList<>(),
+ dummyIkePacketBytes);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequest() throws Exception {
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA,
+ false /*isResp*/,
+ IKE_REKEY_SA_PAYLOAD_HEX_STRING);
+ return makeRekeyIkeRequest(saPayload);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequestWithUnacceptableProposal() throws Exception {
+ IkeSaPayload saPayload =
+ (IkeSaPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_SA,
+ false /*isResp*/,
+ IKE_REKEY_UNACCEPTABLE_SA_PAYLOAD_HEX_STRING);
+ return makeRekeyIkeRequest(saPayload);
+ }
+
+ private ReceivedIkePacket makeRekeyIkeRequest(IkeSaPayload saPayload) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
+
+ payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(NONCE_INIT_PAYLOAD_HEX_STRING);
+
+ List<IkePayload> payloadList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/);
+ payloadList.add(saPayload);
+
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ payloadList);
+ }
+
+ private ReceivedIkePacket makeDeleteIkeRequest(IkeSaRecord saRecord) throws Exception {
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_DELETE);
+
+ payloadHexStringList.add(DELETE_IKE_PAYLOAD_HEX_STRING);
+
+ return makeDummyEncryptedReceivedIkePacket(
+ saRecord,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ payloadTypeList,
+ payloadHexStringList);
+ }
+
+ private ReceivedIkePacket makeResponseWithErrorNotify(IkeNotifyPayload notify)
+ throws Exception {
+ List<IkePayload> payloads = new LinkedList<>();
+ payloads.add(notify);
+ return makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord, EXCHANGE_TYPE_INFORMATIONAL, true /*isResp*/, payloads);
+ }
+
+ private static boolean isIkePayloadExist(
+ List<IkePayload> payloadList, @IkePayload.PayloadType int payloadType) {
+ for (IkePayload payload : payloadList) {
+ if (payload.payloadType == payloadType) return true;
+ }
+ return false;
+ }
+
+ private static boolean isNotifyExist(
+ List<IkePayload> payloadList, @IkeNotifyPayload.NotifyType int notifyType) {
+ for (IkeNotifyPayload notify :
+ IkePayload.getPayloadListForTypeInProvidedList(
+ PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class, payloadList)) {
+ if (notify.notifyType == notifyType) return true;
+ }
+ return false;
+ }
+
+ private void verifyIncrementLocaReqMsgId() {
+ assertEquals(
+ ++mExpectedCurrentSaLocalReqMsgId,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId());
+ }
+
+ private void verifyIncrementRemoteReqMsgId() {
+ assertEquals(
+ ++mExpectedCurrentSaRemoteReqMsgId,
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId());
+ }
+
+ private void verifyRetransmissionStarted() {
+ assertTrue(
+ mIkeSessionStateMachine
+ .getHandler()
+ .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT));
+ }
+
+ private void verifyRetransmissionStopped() {
+ assertFalse(
+ mIkeSessionStateMachine
+ .getHandler()
+ .hasMessages(IkeSessionStateMachine.CMD_RETRANSMIT));
+ }
+
+ private IkeMessage verifyEncryptAndEncodeAndGetMessage(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper)
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ mIkeMessageCaptor.capture(),
+ anyBoolean(),
+ anyInt());
+ return mIkeMessageCaptor.getValue();
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled(IkeSaRecord ikeSaRecord) {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ eq(ikeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void verifyEncryptAndEncodeNeverCalled() {
+ verify(mMockIkeMessageHelper, never())
+ .encryptAndEncode(
+ anyObject(),
+ anyObject(),
+ any(IkeSaRecord.class),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt());
+ }
+
+ private void resetMockIkeMessageHelper() {
+ reset(mMockIkeMessageHelper);
+ when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]);
+ when(mMockIkeMessageHelper.encryptAndEncode(
+ any(), any(), any(), any(), anyBoolean(), anyInt()))
+ .thenReturn(new byte[1][0]);
+ }
+
+ @Test
+ public void testQuit() {
+ mIkeSessionStateMachine.quit();
+ mLooper.dispatchAll();
+
+ verify(mSpyIkeSocket).releaseReference(eq(mIkeSessionStateMachine));
+ verify(mSpyIkeSocket).close();
+ }
+
+ @Test
+ public void testAllocateIkeSpi() throws Exception {
+ // Test randomness.
+ IkeSecurityParameterIndex ikeSpiOne =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+ IkeSecurityParameterIndex ikeSpiTwo =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+
+ assertNotEquals(ikeSpiOne.getSpi(), ikeSpiTwo.getSpi());
+ ikeSpiTwo.close();
+
+ // Test duplicate SPIs.
+ long spiValue = ikeSpiOne.getSpi();
+ try {
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+ fail("Expected to fail because duplicate SPI was assigned to the same address.");
+ } catch (IOException expected) {
+
+ }
+
+ ikeSpiOne.close();
+ IkeSecurityParameterIndex ikeSpiThree =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(LOCAL_ADDRESS, spiValue);
+ ikeSpiThree.close();
+ }
+
+ private void setupFirstIkeSa() throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 2);
+ return mSpyCurrentIkeSaRecord;
+ });
+ }
+
+ private void setupRekeyedIkeSa(IkeSaRecord rekeySaRecord) throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
+ eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 4);
+ return rekeySaRecord;
+ });
+ }
+
+ private void throwExceptionWhenMakeRekeyIkeSa(Exception exception) throws Exception {
+ // Inject IkeSaRecord and release IKE SPI resource since we will lose their references
+ // later.
+ when(mMockSaRecordHelper.makeRekeyedIkeSaRecord(
+ eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ captureAndReleaseIkeSpiResource(invocation, 4);
+ throw exception;
+ });
+ }
+
+ private void captureAndReleaseIkeSpiResource(InvocationOnMock invocation, int ikeConfigIndex) {
+ IkeSaRecordConfig config = (IkeSaRecordConfig) invocation.getArguments()[ikeConfigIndex];
+ config.initSpi.close();
+ config.respSpi.close();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeInit() throws Exception {
+ setupFirstIkeSa();
+
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Receive IKE INIT response
+ ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ // Validate outbound IKE INIT request
+ verify(mMockIkeMessageHelper, times(2)).encode(mIkeMessageCaptor.capture());
+ IkeMessage ikeInitReqMessage = mIkeMessageCaptor.getValue();
+
+ IkeHeader ikeHeader = ikeInitReqMessage.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ List<IkePayload> payloadList = ikeInitReqMessage.ikePayloadList;
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_KE));
+ assertTrue(isIkePayloadExist(payloadList, IkePayload.PAYLOAD_TYPE_NONCE));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
+ assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED));
+
+ verify(mSpyIkeSocket)
+ .registerIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+
+ verify(mMockIkeMessageHelper)
+ .decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Validate negotiated SA proposal.
+ IkeSaProposal negotiatedProposal = mIkeSessionStateMachine.mSaProposal;
+ assertNotNull(negotiatedProposal);
+
+ assertEquals(
+ new EncryptionTransform[] {mIkeEncryptionTransform},
+ negotiatedProposal.getEncryptionTransforms());
+ assertEquals(
+ new IntegrityTransform[] {mIkeIntegrityTransform},
+ negotiatedProposal.getIntegrityTransforms());
+ assertEquals(new PrfTransform[] {mIkePrfTransform}, negotiatedProposal.getPrfTransforms());
+
+ // Validate current IkeSaRecord.
+ verify(mMockSaRecordHelper)
+ .makeFirstIkeSaRecord(
+ any(IkeMessage.class),
+ any(IkeMessage.class),
+ mIkeSaRecordConfigCaptor.capture());
+
+ IkeSaRecordConfig ikeSaRecordConfig = mIkeSaRecordConfigCaptor.getValue();
+ assertEquals(KEY_LEN_IKE_PRF, ikeSaRecordConfig.prf.getKeyLength());
+ assertEquals(KEY_LEN_IKE_INTE, ikeSaRecordConfig.integrityKeyLength);
+ assertEquals(KEY_LEN_IKE_ENCR, ikeSaRecordConfig.encryptionKeyLength);
+ assertEquals(CMD_LOCAL_REQUEST_REKEY_IKE, ikeSaRecordConfig.futureRekeyEvent.procedureType);
+
+ // Validate NAT detection
+ assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat);
+ assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat);
+
+ // Validate fragmentation support negotiation
+ assertTrue(mIkeSessionStateMachine.mSupportFragment);
+ }
+
+ private void setIkeInitResults() throws Exception {
+ mIkeSessionStateMachine.mIkeCipher = mock(IkeCipher.class);
+ mIkeSessionStateMachine.mIkeIntegrity = mock(IkeMacIntegrity.class);
+ mIkeSessionStateMachine.mIkePrf = mock(IkeMacPrf.class);
+ mIkeSessionStateMachine.mSaProposal = buildSaProposal();
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyCurrentIkeSaRecord;
+ mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS;
+ mIkeSessionStateMachine.mIsLocalBehindNat = true;
+ mIkeSessionStateMachine.mIsRemoteBehindNat = false;
+ mIkeSessionStateMachine.mSupportFragment = true;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyCurrentIkeSaRecord);
+ }
+
+ /** Initializes the mIkeSessionStateMachine in the IDLE state. */
+ private void setupIdleStateMachine() throws Exception {
+ setIkeInitResults();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ mDummyChildSmCallback =
+ createChildAndGetChildSessionSmCallback(
+ mMockChildSessionStateMachine, CHILD_SPI_REMOTE, mMockChildSessionCallback);
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ private void mockIkeInitAndTransitionToIkeAuth(State authState) throws Exception {
+ setIkeInitResults();
+
+ // Need to create a real IkeMacPrf instance for authentication because we cannot inject a
+ // method stub for IkeMacPrf#signBytes. IkeMacPrf#signBytes is inheritted from a package
+ // protected class IkePrf. We don't have the visibility to mock it.
+ mIkeSessionStateMachine.mIkePrf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+
+ mIkeSessionStateMachine.mIkeInitRequestBytes = new byte[0];
+ mIkeSessionStateMachine.mIkeInitResponseBytes = new byte[0];
+ mIkeSessionStateMachine.mIkeInitNoncePayload = new IkeNoncePayload();
+ mIkeSessionStateMachine.mIkeRespNoncePayload = new IkeNoncePayload();
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_FORCE_TRANSITION, authState);
+ mLooper.dispatchAll();
+ }
+
+ private void setupChildStateMachineFactory(ChildSessionStateMachine child) {
+ // After state machine start, add to the callback->statemachine map
+ when(mMockChildSessionFactoryHelper.makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ any(ChildSessionCallback.class),
+ any(IChildSessionSmCallback.class)))
+ .thenReturn(child);
+ }
+
+ /**
+ * Utility to register a new callback -> state machine mapping.
+ *
+ * <p>Must be used if IkeSessionStateMachine.openChildSession() is not called, but commands
+ * injected instead.
+ *
+ * @param callback The callback to be used for the mapping
+ * @param sm The ChildSessionStateMachine instance to be used.
+ */
+ private void registerChildStateMachine(
+ ChildSessionCallback callback, ChildSessionStateMachine sm) {
+ setupChildStateMachineFactory(sm);
+ mIkeSessionStateMachine.registerChildSessionCallback(
+ mChildSessionOptions, callback, false /*isFirstChild*/);
+ }
+
+ @Test
+ public void testCreateAdditionalChild() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback childCallback = mock(ChildSessionCallback.class);
+ ChildSessionStateMachine childStateMachine = mock(ChildSessionStateMachine.class);
+ registerChildStateMachine(childCallback, childStateMachine);
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD,
+ childCallback,
+ mChildSessionOptions));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(childStateMachine)
+ .createChildSession(
+ eq(LOCAL_ADDRESS),
+ eq(REMOTE_ADDRESS),
+ any(), // udpEncapSocket
+ eq(mIkeSessionStateMachine.mIkePrf),
+ any()); // sk_d
+
+ // Once for initial child, a second time for the additional child.
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(childCallback),
+ mChildSessionSmCbCaptor.capture());
+ IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
+
+ // Mocking sending request
+ cb.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ new LinkedList<>(),
+ childStateMachine);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ IkeMessage createChildRequest = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = createChildRequest.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+ assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId);
+ assertTrue(createChildRequest.ikePayloadList.isEmpty());
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ // Mocking receiving response
+ ReceivedIkePacket dummyCreateChildResp = makeCreateChildCreateMessage(true /*isResp*/);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyCreateChildResp);
+ mLooper.dispatchAll();
+
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyCreateChildResp);
+
+ verify(childStateMachine)
+ .receiveResponse(
+ eq(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA), mPayloadListCaptor.capture());
+
+ List<IkePayload> childRespList = mPayloadListCaptor.getValue();
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE));
+
+ // Mock finishing procedure
+ cb.onProcedureFinished(childStateMachine);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testTriggerDeleteChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).deleteChildSession();
+ }
+
+ @Test
+ public void testHandleDeleteChildBeforeCreation() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
+ mock(ChildSessionCallback.class),
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testTriggerRekeyChildLocal() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+ }
+
+ @Test
+ public void testScheduleAndTriggerRekeyChildLocal() throws Exception {
+ setupIdleStateMachine();
+ long dummyRekeyTimeout = 10000L;
+
+ ChildLocalRequest rekeyRequest =
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/);
+ mDummyChildSmCallback.scheduleLocalRequest(rekeyRequest, dummyRekeyTimeout);
+
+ mLooper.moveTimeForward(dummyRekeyTimeout);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+ }
+
+ private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
+ ChildSessionStateMachine child, int remoteSpi) throws Exception {
+ return createChildAndGetChildSessionSmCallback(
+ child, remoteSpi, mock(ChildSessionCallback.class));
+ }
+
+ private IChildSessionSmCallback createChildAndGetChildSessionSmCallback(
+ ChildSessionStateMachine child, int remoteSpi, ChildSessionCallback childCallback)
+ throws Exception {
+ registerChildStateMachine(childCallback, child);
+
+ IChildSessionSmCallback cb = mIkeSessionStateMachine.new ChildSessionSmCallback();
+ cb.onChildSaCreated(remoteSpi, child);
+ mLooper.dispatchAll();
+
+ return cb;
+ }
+
+ private void transitionToChildProcedureOngoing() {
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mChildProcedureOngoing);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ private void verifyChildReceiveDeleteRequest(
+ ChildSessionStateMachine child, IkeDeletePayload[] expectedDelPayloads) {
+ verify(child)
+ .receiveRequest(
+ eq(IKE_EXCHANGE_SUBTYPE_DELETE_CHILD),
+ eq(EXCHANGE_TYPE_INFORMATIONAL),
+ mPayloadListCaptor.capture());
+ List<IkePayload> reqPayloads = mPayloadListCaptor.getValue();
+
+ int numExpectedDelPayloads = expectedDelPayloads.length;
+ assertEquals(numExpectedDelPayloads, reqPayloads.size());
+
+ for (int i = 0; i < numExpectedDelPayloads; i++) {
+ assertEquals(expectedDelPayloads[i], (IkeDeletePayload) reqPayloads.get(i));
+ }
+ }
+
+ private void outboundDeleteChildPayloadsReady(
+ IChildSessionSmCallback childSmCb,
+ IkeDeletePayload delPayload,
+ boolean isResp,
+ ChildSessionStateMachine child) {
+ List<IkePayload> outPayloadList = new LinkedList<>();
+ outPayloadList.add(delPayload);
+ childSmCb.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, isResp, outPayloadList, child);
+ mLooper.dispatchAll();
+ }
+
+ private List<IkePayload> verifyOutInfoMsgHeaderAndGetPayloads(boolean isResp) {
+ IkeMessage deleteChildMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = deleteChildMessage.ikeHeader;
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertEquals(isResp, ikeHeader.isResponseMsg);
+
+ return deleteChildMessage.ikePayloadList;
+ }
+
+ @Test
+ public void testDeferChildRequestToChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+ }
+
+ @Test
+ public void testRemoteDeleteOneChild() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // Outbound payload list ready
+ IkeDeletePayload outDelPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback,
+ outDelPayload,
+ true /*isResp*/,
+ mMockChildSessionStateMachine);
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testRemoteDeleteMultipleChildSession() throws Exception {
+ ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class);
+ int childOneRemoteSpi = 11;
+ int childOneLocalSpi = 12;
+
+ ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class);
+ int childTwoRemoteSpi = 21;
+ int childTwoLocalSpi = 22;
+
+ setupIdleStateMachine();
+ IChildSessionSmCallback childSmCbOne =
+ createChildAndGetChildSessionSmCallback(childOne, childOneRemoteSpi);
+ IChildSessionSmCallback childSmCbTwo =
+ createChildAndGetChildSessionSmCallback(childTwo, childTwoRemoteSpi);
+
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {childOneRemoteSpi, childTwoRemoteSpi})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(childOne, inboundDelPayloads);
+ verifyChildReceiveDeleteRequest(childTwo, inboundDelPayloads);
+
+ // childOne outbound payload list ready
+ IkeDeletePayload outDelPayloadOne = new IkeDeletePayload(new int[] {childOneLocalSpi});
+ outboundDeleteChildPayloadsReady(childSmCbOne, outDelPayloadOne, true /*isResp*/, childOne);
+ mLooper.dispatchAll();
+
+ // Verify that no response is sent
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // childTwo outbound payload list ready
+ IkeDeletePayload outDelPayloadTwo = new IkeDeletePayload(new int[] {childTwoLocalSpi});
+ outboundDeleteChildPayloadsReady(childSmCbTwo, outDelPayloadTwo, true /*isResp*/, childTwo);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(2, payloadList.size());
+ assertEquals(outDelPayloadOne, ((IkeDeletePayload) payloadList.get(0)));
+ assertEquals(outDelPayloadTwo, ((IkeDeletePayload) payloadList.get(1)));
+ }
+
+ @Test
+ public void testRemoteDeleteMultipleChildSaInSameSession() throws Exception {
+ int newChildRemoteSpi = 21;
+ int newChildLocalSpi = 22;
+
+ setupIdleStateMachine();
+ mDummyChildSmCallback.onChildSaCreated(newChildRemoteSpi, mMockChildSessionStateMachine);
+
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE}),
+ new IkeDeletePayload(new int[] {newChildRemoteSpi})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // child outbound payload list ready
+ IkeDeletePayload outDelPayload =
+ new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL, newChildLocalSpi});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback,
+ outDelPayload,
+ true /*isResp*/,
+ mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outDelPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testIgnoreUnrecognizedChildSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ // Receive Delete Child Request
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {
+ new IkeDeletePayload(new int[] {unrecognizedSpi, CHILD_SPI_REMOTE})
+ };
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify received payloads
+ verifyChildReceiveDeleteRequest(mMockChildSessionStateMachine, inboundDelPayloads);
+
+ // child outbound payload list ready
+ IkeDeletePayload outPayload = new IkeDeletePayload(new int[] {CHILD_SPI_LOCAL});
+ outboundDeleteChildPayloadsReady(
+ mDummyChildSmCallback, outPayload, true /*isResp*/, mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+
+ // Verify outbound response
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloadList.size());
+ assertEquals(outPayload, ((IkeDeletePayload) payloadList.get(0)));
+ }
+
+ @Test
+ public void testRemoteDeleteChildHandlesReqWithNoRecognizedSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+
+ // Receive Delete Child Request without any recognized SPI
+ IkeDeletePayload[] inboundDelPayloads =
+ new IkeDeletePayload[] {new IkeDeletePayload(new int[] {unrecognizedSpi})};
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/));
+ mLooper.dispatchAll();
+
+ // Verify outbound empty response was sent
+ List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertTrue(payloadList.isEmpty());
+
+ // Verify IKE Session was back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRemoteCreateChild() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET, makeCreateChildCreateMessage(false /*isResp*/));
+
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_NO_ADDITIONAL_SAS,
+ ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+ }
+
+ @Test
+ public void testTriggerRemoteRekeyChild() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET,
+ makeRekeyChildCreateMessage(false /*isResp*/, CHILD_SPI_REMOTE));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionStateMachine)
+ .receiveRequest(
+ eq(IKE_EXCHANGE_SUBTYPE_REKEY_CHILD),
+ eq(EXCHANGE_TYPE_CREATE_CHILD_SA),
+ any(List.class));
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ @Test
+ public void testHandleRekeyChildReqWithUnrecognizedSpi() throws Exception {
+ int unrecognizedSpi = 2;
+
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET,
+ makeRekeyChildCreateMessage(false /*isResp*/, unrecognizedSpi));
+ mLooper.dispatchAll();
+
+ verify(mMockChildSessionStateMachine, never()).receiveRequest(anyInt(), anyInt(), any());
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ IkeNotifyPayload notifyPayload = (IkeNotifyPayload) ikePayloadList.get(0);
+ assertEquals(ERROR_TYPE_CHILD_SA_NOT_FOUND, notifyPayload.notifyType);
+ assertEquals(unrecognizedSpi, notifyPayload.spi);
+ }
+
+ private void verifyNotifyUserCloseSession() {
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosed();
+ }
+
+ @Test
+ public void testRcvRemoteDeleteIkeWhenChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ mIkeSessionStateMachine.sendMessage(
+ CMD_RECEIVE_IKE_PACKET, makeDeleteIkeRequest(mSpyCurrentIkeSaRecord));
+
+ mLooper.dispatchAll();
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertTrue(ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testRcvRemoteRekeyIkeWhenChildProcedureOngoing() throws Exception {
+ setupIdleStateMachine();
+ transitionToChildProcedureOngoing();
+
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, makeRekeyIkeRequest());
+
+ mLooper.dispatchAll();
+
+ // Since we have forced state machine to transition to ChildProcedureOngoing state without
+ // really starting any Child procedure, it should transition to Idle at this time.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_TEMPORARY_FAILURE,
+ ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+ }
+
+ @Test
+ public void testKillChildSessions() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionStateMachine childOne = mock(ChildSessionStateMachine.class);
+ ChildSessionStateMachine childTwo = mock(ChildSessionStateMachine.class);
+ registerChildStateMachine(mock(ChildSessionCallback.class), childOne);
+ registerChildStateMachine(mock(ChildSessionCallback.class), childTwo);
+
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = null;
+
+ mIkeSessionStateMachine.quitNow();
+
+ mLooper.dispatchAll();
+
+ verify(childOne).killSession();
+ verify(childTwo).killSession();
+ }
+
+ private IkeMessage verifyAuthReqAndGetMsg() {
+ IkeMessage ikeAuthReqMessage = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader;
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ return ikeAuthReqMessage;
+ }
+
+ private IkeMessage verifyAuthReqWithChildPayloadsAndGetMsg() {
+ IkeMessage ikeAuthReqMessage = verifyAuthReqAndGetMsg();
+
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_ID_INITIATOR, IkeIdPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER, IkeIdPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_TS_INITIATOR, IkeTsPayload.class));
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_TS_RESPONDER, IkeTsPayload.class));
+
+ return ikeAuthReqMessage;
+ }
+
+ private void verifySharedKeyAuthentication(
+ IkeAuthPskPayload spyAuthPayload,
+ IkeIdPayload respIdPayload,
+ List<IkePayload> authRelatedPayloads,
+ boolean hasChildPayloads)
+ throws Exception {
+ // Send IKE AUTH response to IKE state machine
+ ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
+ mLooper.dispatchAll();
+
+ // Validate outbound IKE AUTH request
+ IkeMessage ikeAuthReqMessage;
+ if (hasChildPayloads) {
+ ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg();
+ } else {
+ ikeAuthReqMessage = verifyAuthReqAndGetMsg();
+ }
+ assertNotNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPskPayload.class));
+
+ // Validate inbound IKE AUTH response
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp);
+
+ // Validate authentication is done. Cannot use matchers because IkeAuthPskPayload is final.
+ verify(spyAuthPayload)
+ .verifyInboundSignature(
+ mPsk,
+ mIkeSessionStateMachine.mIkeInitRequestBytes,
+ mSpyCurrentIkeSaRecord.nonceInitiator,
+ respIdPayload.getEncodedPayloadBody(),
+ mIkeSessionStateMachine.mIkePrf,
+ mSpyCurrentIkeSaRecord.getSkPr());
+
+ // Validate that user has been notified
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onOpened(any());
+ // TODO: Verify sessionConfiguration
+
+ // Verify payload list pair for first Child negotiation
+ ArgumentCaptor<List<IkePayload>> mReqPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ ArgumentCaptor<List<IkePayload>> mRespPayloadListCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mMockChildSessionStateMachine)
+ .handleFirstChildExchange(
+ mReqPayloadListCaptor.capture(),
+ mRespPayloadListCaptor.capture(),
+ eq(LOCAL_ADDRESS),
+ eq(REMOTE_ADDRESS),
+ any(), // udpEncapSocket
+ eq(mIkeSessionStateMachine.mIkePrf),
+ any()); // sk_d
+ List<IkePayload> childReqList = mReqPayloadListCaptor.getValue();
+ List<IkePayload> childRespList = mRespPayloadListCaptor.getValue();
+
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childReqList, IkePayload.PAYLOAD_TYPE_NONCE));
+ IkeSaPayload reqSaPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childReqList);
+ assertFalse(reqSaPayload.isSaResponse);
+
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_SA));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_INITIATOR));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_TS_RESPONDER));
+ assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_NONCE));
+ IkeSaPayload respSaPayload =
+ IkePayload.getPayloadForTypeInProvidedList(
+ IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class, childRespList);
+ assertTrue(respSaPayload.isSaResponse);
+
+ // Mock finishing first Child SA negotiation.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(mMockChildSessionCallback),
+ mChildSessionSmCbCaptor.capture());
+ IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue();
+
+ cb.onProcedureFinished(mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ private IkeAuthPskPayload makeSpyRespPskPayload() throws Exception {
+ IkeAuthPskPayload spyAuthPayload =
+ spy(
+ (IkeAuthPskPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ true /*isResp*/,
+ PSK_AUTH_RESP_PAYLOAD_HEX_STRING));
+
+ doNothing()
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ return spyAuthPayload;
+ }
+
+ private IkeAuthDigitalSignPayload makeSpyDigitalSignAuthPayload() throws Exception {
+ IkeAuthDigitalSignPayload spyAuthPayload =
+ spy(
+ (IkeAuthDigitalSignPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ true /*isResp*/,
+ GENERIC_DIGITAL_SIGN_AUTH_RESP_HEX_STRING));
+ doNothing()
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ return spyAuthPayload;
+ }
+
+ private IkeIdPayload makeRespIdPayload() throws Exception {
+ return (IkeIdPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
+ true /*isResp*/,
+ ID_PAYLOAD_RESPONDER_HEX_STRING);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPsk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, true);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPskVerifyFail() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Build IKE AUTH response with invalid Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ doThrow(new AuthenticationFailedException("DummyAuthFailException"))
+ .when(spyAuthPayload)
+ .verifyInboundSignature(any(), any(), any(), any(), any(), any());
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ // Send response to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session was closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+ }
+
+ @Test
+ public void testAuthPskHandleRespWithParsingError() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord, true /*isResp*/, IkeHeader.EXCHANGE_TYPE_IKE_AUTH);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPreEap() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Mock IKE INIT
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with EAP. Auth, ID-Resp and Cert payloads.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+
+ authRelatedPayloads.add(new IkeEapPayload(EAP_DUMMY_MSG));
+ authRelatedPayloads.add(makeSpyDigitalSignAuthPayload());
+ authRelatedPayloads.add(makeRespIdPayload());
+
+ IkeCertX509CertPayload certPayload = new IkeCertX509CertPayload(mServerEndCertificate);
+ authRelatedPayloads.add(certPayload);
+
+ // Send IKE AUTH response to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithoutChildPayloads(authRelatedPayloads));
+ mLooper.dispatchAll();
+
+ // Validate outbound IKE AUTH request
+ IkeMessage ikeAuthReqMessage = verifyAuthReqWithChildPayloadsAndGetMsg();
+ assertNull(
+ ikeAuthReqMessage.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPayload.class));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthInEap);
+ verifyRetransmissionStopped();
+ assertNotNull(mIkeSessionStateMachine.mInitIdPayload);
+ assertNotNull(mIkeSessionStateMachine.mRespIdPayload);
+ }
+
+ private IEapCallback verifyEapAuthenticatorCreatedAndGetCallback() {
+ ArgumentCaptor<IEapCallback> captor = ArgumentCaptor.forClass(IEapCallback.class);
+
+ verify(mMockEapAuthenticatorFactory)
+ .newEapAuthenticator(
+ eq(mIkeSessionStateMachine.getHandler().getLooper()),
+ captor.capture(),
+ eq(mContext),
+ eq(mEapSessionConfig));
+
+ return captor.getValue();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapStartsAuthenticatorAndProxiesMessage()
+ throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EAP_START_EAP_AUTH, new IkeEapPayload(EAP_DUMMY_MSG));
+ mLooper.dispatchAll();
+
+ verifyEapAuthenticatorCreatedAndGetCallback();
+
+ verify(mMockEapAuthenticator).processEapMessage(eq(EAP_DUMMY_MSG));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesOutboundResponse() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onResponse(EAP_DUMMY_MSG);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify EAP response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ assertEquals(1, resp.ikePayloadList.size());
+ assertArrayEquals(EAP_DUMMY_MSG, ((IkeEapPayload) resp.ikePayloadList.get(0)).eapMessage);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesMissingEapPacket() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ // Mock sending IKE_AUTH{EAP} request
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onResponse(EAP_DUMMY_MSG);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Send IKE AUTH response with no EAP Payload to IKE state machine
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeIkeAuthRespWithoutChildPayloads(new LinkedList<>()));
+ mLooper.dispatchAll();
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesSuccess() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+
+ // Setup dummy initIdPayload for next state.
+ mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class);
+ when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody())
+ .thenReturn(new byte[0]);
+
+ callback.onSuccess(mPsk, new byte[0]); // use mPsk as MSK, eMSK does not matter
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthPostEap);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesError() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+
+ Throwable error = new IllegalArgumentException();
+ callback.onError(error);
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(argThat(err -> err.getCause() == error));
+
+ // Verify state machine quit properly
+ verify(mSpyCurrentIkeSaRecord).close();
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthInEapHandlesFailure() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
+ mLooper.dispatchAll();
+
+ IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback();
+ callback.onFail();
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(any(AuthenticationFailedException.class));
+
+ // Verify state machine quit properly
+ verify(mSpyCurrentIkeSaRecord).close();
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthPostEap() throws Exception {
+ mIkeSessionStateMachine.quitNow();
+ reset(mMockChildSessionFactoryHelper);
+ setupChildStateMachineFactory(mMockChildSessionStateMachine);
+ mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionOptionsEap());
+
+ // Setup dummy state from IkeAuthPreEap for next state.
+ mIkeSessionStateMachine.mInitIdPayload = mock(IkeIdPayload.class);
+ when(mIkeSessionStateMachine.mInitIdPayload.getEncodedPayloadBody())
+ .thenReturn(new byte[0]);
+ mIkeSessionStateMachine.mRespIdPayload =
+ (IkeIdPayload)
+ IkeTestUtils.hexStringToIkePayload(
+ IkePayload.PAYLOAD_TYPE_ID_RESPONDER,
+ true /*isResp*/,
+ ID_PAYLOAD_RESPONDER_HEX_STRING);
+
+ List<Integer> payloadTypeList = new LinkedList<>();
+ List<String> payloadHexStringList = new LinkedList<>();
+
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR);
+ payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER);
+
+ payloadHexStringList.add(CHILD_SA_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING);
+ payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING);
+
+ mIkeSessionStateMachine.mFirstChildReqList =
+ hexStrListToIkePayloadList(payloadTypeList, payloadHexStringList, false /*isResp*/);
+
+ // Setup state and go to IN_EAP state
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuthPostEap);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_EAP_FINISH_EAP_AUTH, mPsk);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+
+ verifySharedKeyAuthentication(spyAuthPayload, respIdPayload, authRelatedPayloads, false);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesFirstFrag() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testCreateIkeLocalIkeAuthHandlesFrag".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ skfPayload,
+ PAYLOAD_TYPE_AUTH,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify state doesn't change
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+
+ // Verify the IkeSaRecord has stored the fragment.
+ DecodeResultPartial resultPartial =
+ mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(false /*isResp*/));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragOk() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Build reassembled IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
+ List<IkePayload> authRelatedPayloads = new LinkedList<>();
+ IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
+ authRelatedPayloads.add(spyAuthPayload);
+
+ IkeIdPayload respIdPayload = makeRespIdPayload();
+ authRelatedPayloads.add(respIdPayload);
+
+ List<IkePayload> authPayloadList =
+ getIkeAuthPayloadListWithChildPayloads(authRelatedPayloads);
+
+ // Receive last auth response and do IKE_AUTH
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketOk(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ authPayloadList,
+ "FirstFrag".getBytes());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify IKE AUTH is done
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ // Verify collected response fragments are cleared.
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeAuthHandlesLastFragError() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Set previously collected IKE fragments
+ DecodeResultPartial mockCollectedFrags = mock(DecodeResultPartial.class);
+ mSpyCurrentIkeSaRecord.updateCollectedFragments(mockCollectedFrags, true /*isResp*/);
+
+ // Receive last auth response with syntax error
+ ReceivedIkePacket packet =
+ makeDummyReceivedLastIkeFragmentPacketError(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ mockCollectedFrags,
+ new InvalidSyntaxException("IkeStateMachineTest"));
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify Delete request is sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+
+ // Collected response fragments are cleared
+ assertNull(mSpyCurrentIkeSaRecord.getCollectedFragments(true /*isResp*/));
+ verify(mSpyCurrentIkeSaRecord).resetCollectedFragments(true /*isResp*/);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateSendsRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage rekeyMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = rekeyMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), ikeHeader.messageId);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ // Verify SA payload & proposals
+ IkeSaPayload saPayload =
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class);
+ assertFalse(saPayload.isSaResponse);
+ assertEquals(1, saPayload.proposalList.size());
+
+ IkeSaPayload.IkeProposal proposal =
+ (IkeSaPayload.IkeProposal) saPayload.proposalList.get(0);
+ assertEquals(1, proposal.number); // Must be 1-indexed
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IKE, proposal.spiSize);
+ assertEquals(mIkeSessionStateMachine.mSaProposal, proposal.saProposal);
+
+ // Verify Nonce and KE payloads exist.
+ assertNotNull(
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class));
+
+ IkeKePayload kePayload =
+ rekeyMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class);
+ assertNotNull(kePayload);
+ assertTrue(kePayload.isOutbound);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandlesResponse() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket);
+
+ // Verify in delete state, and new SA record was saved:
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
+ verifyRetransmissionStarted();
+ assertEquals(mSpyLocalInitIkeSaRecord, mIkeSessionStateMachine.mLocalInitNewIkeSaRecord);
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithParsingError() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithNonFatalErrorNotify() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Mock receiving packet with NO_PROPOSAL_CHOSEN
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_NO_PROPOSAL_CHOSEN));
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify IKE Session goes back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleRespWithFatalErrorNotify() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with NO_PROPOSAL_CHOSEN
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX));
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify no message was sent because a fatal error notification was received
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateSaCreationFail() throws Exception {
+ // Throw error when building new IKE SA
+ throwExceptionWhenMakeRekeyIkeSa(
+ new GeneralSecurityException("testRekeyIkeLocalCreateSaCreationFail"));
+
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ resetMockIkeMessageHelper();
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(false /*isResp*/);
+ assertEquals(1, payloads.size());
+ assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, payloads.get(0).payloadType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ }
+
+ @Test
+ public void testRekeyIkeLocalCreateHandleReqWithNonFatalError() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Build protocol exception
+ List<Integer> unsupportedPayloads = new LinkedList<>();
+ unsupportedPayloads.add(PAYLOAD_TYPE_UNSUPPORTED);
+ UnsupportedCriticalPayloadException exception =
+ new UnsupportedCriticalPayloadException(unsupportedPayloads);
+
+ // Mock receiving packet with unsupported critical payload
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithDecodingError(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ exception);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+
+ IkePayload payload = payloads.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(
+ ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD, ((IkeNotifyPayload) payload).notifyType);
+
+ // Verify IKE Session stays in the same state
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ private void mockCreateAndTransitionToRekeyDeleteLocal() {
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyLocalInitIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeLocalDelete);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteSendsRequest() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Verify Rekey-Delete request
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalDelete);
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), ikeHeader.ikeResponderSpi);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertFalse(ikeHeader.isResponseMsg);
+
+ List<IkeDeletePayload> deletePayloadList =
+ delMsg.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class);
+ assertEquals(1, deletePayloadList.size());
+
+ IkeDeletePayload deletePayload = deletePayloadList.get(0);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(0, deletePayload.numSpi);
+ assertEquals(0, deletePayload.spiSize);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ private void verifyRekeyReplaceSa(IkeSaRecord newSaRecord) {
+ verify(mSpyCurrentIkeSaRecord).close();
+ verify(mSpyIkeSocket).unregisterIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()));
+ verify(mSpyIkeSocket, never()).unregisterIke(eq(newSaRecord.getLocalSpi()));
+
+ assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, newSaRecord);
+
+ verify(mMockChildSessionStateMachine).setSkD(newSaRecord.getSkD());
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Receive Delete response
+ ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
+ makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verify(mMockIkeSessionCallback, never()).onClosed();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteHandlesRespWithParsingError() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify no more request out
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Receive an empty (DPD) request on the new IKE SA
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDpdIkeRequest(mSpyLocalInitIkeSaRecord));
+ mLooper.dispatchAll();
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ verifyRetransmissionStopped();
+ }
+
+ @Test
+ public void testRekeyIkeLocalDeleteWithRequestFragOnNewSa() throws Exception {
+ setupIdleStateMachine();
+ mockCreateAndTransitionToRekeyDeleteLocal();
+
+ // Received IKE fragment
+ byte[] unencryptedData = "testRekeyIkeLocalDeleteWithRequestFragOnNewSa".getBytes();
+ int fragNum = 1;
+ int totalFragments = 2;
+ IkeSkfPayload skfPayload =
+ IkeTestUtils.makeDummySkfPayload(unencryptedData, fragNum, totalFragments);
+
+ ReceivedIkePacket packet =
+ makeDummyReceivedIkeFragmentPacket(
+ mSpyLocalInitIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ skfPayload,
+ PAYLOAD_TYPE_SA,
+ null /* collectedFrags*/);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
+ mLooper.dispatchAll();
+
+ // Verify rekey is done.
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verifyRetransmissionStopped();
+
+ // Verify the IkeSaRecord has stored the new fragment.
+ DecodeResultPartial resultPartial =
+ mSpyLocalInitIkeSaRecord.getCollectedFragments(false /*isResp*/);
+ assertEquals(PAYLOAD_TYPE_SA, resultPartial.firstPayloadType);
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[fragNum - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteWithRequestOnNewSa() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeRemoteDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.addIkeSaRecord(mSpyRemoteInitIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Receive an empty (DPD) request on the new IKE SA
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDpdIkeRequest(mSpyRemoteInitIkeSaRecord));
+ mLooper.dispatchAll();
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreate() throws Exception {
+ setupIdleStateMachine();
+
+ setupRekeyedIkeSa(mSpyRemoteInitIkeSaRecord);
+
+ // Receive Rekey request
+ ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket);
+
+ // Verify SA created with correct parameters
+ ArgumentCaptor<SaRecord.IkeSaRecordConfig> recordConfigCaptor =
+ ArgumentCaptor.forClass(SaRecord.IkeSaRecordConfig.class);
+ verify(mMockSaRecordHelper)
+ .makeRekeyedIkeSaRecord(any(), any(), any(), any(), recordConfigCaptor.capture());
+ assertEquals(IKE_REKEY_SA_INITIATOR_SPI, recordConfigCaptor.getValue().initSpi.getSpi());
+
+ // Verify outbound CREATE_CHILD_SA message
+ IkeMessage rekeyCreateResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader rekeyCreateRespHeader = rekeyCreateResp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyCreateRespHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, rekeyCreateRespHeader.exchangeType);
+ assertTrue(rekeyCreateRespHeader.isResponseMsg);
+ assertTrue(rekeyCreateRespHeader.fromIkeInitiator);
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_SA, IkeSaPayload.class));
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class));
+ assertNotNull(
+ rekeyCreateResp.getPayloadForType(
+ IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class));
+
+ // Verify SA, StateMachine state
+ assertEquals(mSpyCurrentIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordAwaitingRemoteDel);
+ assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mIkeSaRecordSurviving);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyRemoteInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreateHandlesInvalidReq() throws Exception {
+ setupIdleStateMachine();
+
+ // Receive Rekey request
+ ReceivedIkePacket request = makeRekeyIkeRequestWithUnacceptableProposal();
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteCreateSaCreationFailure() throws Exception {
+ // Throw error when building new IKE SA
+ throwExceptionWhenMakeRekeyIkeSa(
+ new GeneralSecurityException("testRekeyIkeRemoteCreateSaCreationFailure"));
+ setupIdleStateMachine();
+
+ // Receive Rekey request
+ ReceivedIkePacket request = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
+ }
+
+ private void verifyProcessRekeyReqFailure(int expectedErrorCode) {
+ // Verify IKE Session is back to Idle
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ // Verify error notification was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+ IkeNotifyPayload notify = (IkeNotifyPayload) payloads.get(0);
+ assertEquals(expectedErrorCode, notify.notifyType);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Rekey Delete request
+ ReceivedIkePacket dummyDeleteIkeRequestReceivedPacket =
+ makeDeleteIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
+
+ // Verify outbound DELETE_IKE_SA message
+ IkeMessage rekeyDeleteResp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader rekeyDeleteRespHeader = rekeyDeleteResp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, rekeyDeleteRespHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, rekeyDeleteRespHeader.exchangeType);
+ assertTrue(rekeyDeleteRespHeader.isResponseMsg);
+ assertTrue(rekeyDeleteRespHeader.fromIkeInitiator);
+ assertTrue(rekeyDeleteResp.ikePayloadList.isEmpty());
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+
+ verify(mMockIkeSessionCallback, never()).onClosed();
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteExitAndRenter() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Trigger a timeout, and immediately re-enter remote-delete
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.TIMEOUT_REKEY_REMOTE_DELETE);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // Shift time forward, and assert the previous timeout was NOT fired.
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS / 2 + 1);
+ mLooper.dispatchAll();
+
+ // Verify no request received, or response sent.
+ verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state has not changed - signal was not sent.
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+ }
+
+ @Test
+ public void testRekeyIkeRemoteDeleteTimedOut() throws Exception {
+ setupIdleStateMachine();
+
+ // Seed fake rekey data and force transition to RekeyIkeLocalDelete
+ mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ mLooper.moveTimeForward(IkeSessionStateMachine.REKEY_DELETE_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Verify no request received, or response sent.
+ verify(mMockIkeMessageHelper, never()).decode(anyInt(), anyObject(), anyObject());
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify final state - Idle, with new SA, and old SA closed.
+ verifyRekeyReplaceSa(mSpyRemoteInitIkeSaRecord);
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testSimulRekey() throws Exception {
+ setupIdleStateMachine();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+ when(mSpyLocalInitIkeSaRecord.compareTo(mSpyRemoteInitIkeSaRecord)).thenReturn(1);
+
+ // Send Rekey request on mSpyCurrentIkeSaRecord
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+
+ // Receive Rekey request on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyRekeyIkeRequestReceivedPacket = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRequestReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+
+ // Receive Rekey response on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+ verify(mSpyIkeSocket)
+ .registerIke(
+ eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mIkeSessionStateMachine));
+
+ // Receive Delete response on mSpyCurrentIkeSaRecord
+ ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
+ makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ // Verify
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRequestReceivedPacket);
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyRekeyIkeRespReceivedPacket);
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRespReceivedPacket);
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ verifyRekeyReplaceSa(mSpyLocalInitIkeSaRecord);
+ verify(mMockIkeSessionCallback, never()).onClosed();
+ }
+
+ @Test
+ public void testOpenIkeSession() throws Exception {
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.Initial);
+
+ mIkeSessionStateMachine.openSession();
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeInit);
+ }
+
+ @Test
+ public void testIkeInitSchedulesRekey() throws Exception {
+ setupFirstIkeSa();
+
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+
+ // Receive IKE INIT response
+ ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
+
+ // Mock IKE AUTH and transition to Idle
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ mIkeSessionStateMachine.mSaProposal = buildSaProposal();
+
+ // Move time forward to trigger rekey
+ mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testRekeyCreateIkeSchedulesRekey() throws Exception {
+ setupIdleStateMachine();
+
+ // Send Rekey-Create request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Prepare "rekeyed" SA
+ setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
+
+ // Receive Rekey response
+ ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
+ mLooper.dispatchAll();
+
+ // Mock rekey delete and transition to Idle
+ mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyLocalInitIkeSaRecord;
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ // Move time forward to trigger rekey
+ mLooper.moveTimeForward(SA_SOFT_LIFETIME_MS);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ }
+
+ @Test
+ public void testBuildEncryptedInformationalMessage() throws Exception {
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_INVALID_SYNTAX, new byte[0]);
+
+ boolean isResp = false;
+ IkeMessage generated =
+ mIkeSessionStateMachine.buildEncryptedInformationalMessage(
+ mSpyCurrentIkeSaRecord, new IkeInformationalPayload[] {payload}, isResp, 0);
+
+ assertEquals(mSpyCurrentIkeSaRecord.getInitiatorSpi(), generated.ikeHeader.ikeInitiatorSpi);
+ assertEquals(mSpyCurrentIkeSaRecord.getResponderSpi(), generated.ikeHeader.ikeResponderSpi);
+ assertEquals(
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId(), generated.ikeHeader.messageId);
+ assertEquals(isResp, generated.ikeHeader.isResponseMsg);
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, generated.ikeHeader.nextPayloadType);
+
+ List<IkeNotifyPayload> generatedPayloads =
+ generated.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
+ assertEquals(1, generatedPayloads.size());
+
+ IkeNotifyPayload generatedPayload = generatedPayloads.get(0);
+ assertArrayEquals(new byte[0], generatedPayload.notifyData);
+ assertEquals(ERROR_TYPE_INVALID_SYNTAX, generatedPayload.notifyType);
+ }
+
+ private void verifyLastSentRespAllPackets(byte[][] expectedPackets, IkeSaRecord saRecord) {
+ if (expectedPackets == null) {
+ assertNull(saRecord.getLastSentRespAllPackets());
+ return;
+ }
+
+ assertEquals(expectedPackets.length, saRecord.getLastSentRespAllPackets().size());
+ for (int i = 0; i < expectedPackets.length; i++) {
+ assertArrayEquals(expectedPackets[i], saRecord.getLastSentRespAllPackets().get(i));
+ }
+ }
+
+ @Test
+ public void testEncryptedRetransmitterImmediatelySendsRequest() throws Exception {
+ setupIdleStateMachine();
+ byte[][] dummyLastRespBytes =
+ new byte[][] {"testRetransmitterSendsRequestLastResp".getBytes()};
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyLastRespBytes));
+
+ IkeMessage spyIkeReqMessage =
+ spy(
+ new IkeMessage(
+ new IkeHeader(
+ mSpyCurrentIkeSaRecord.getInitiatorSpi(),
+ mSpyCurrentIkeSaRecord.getResponderSpi(),
+ IkePayload.PAYLOAD_TYPE_SK,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ false /*isResp*/,
+ mSpyCurrentIkeSaRecord.isLocalInit,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId()),
+ new LinkedList<>()));
+
+ // Use something unique as a sentinel value
+ byte[][] dummyReqBytesList =
+ new byte[][] {
+ "testRetransmitterSendsReqFrag1".getBytes(),
+ "testRetransmitterSendsReqFrag2".getBytes()
+ };
+
+ doReturn(dummyReqBytesList)
+ .when(spyIkeReqMessage)
+ .encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
+
+ IkeSessionStateMachine.EncryptedRetransmitter retransmitter =
+ mIkeSessionStateMachine.new EncryptedRetransmitter(spyIkeReqMessage);
+
+ // Verify message is sent out, and that request does not change cached retransmit-response
+ // mLastSentIkeResp.
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyReqBytesList[1]), eq(REMOTE_ADDRESS));
+ verifyLastSentRespAllPackets(dummyLastRespBytes, mSpyCurrentIkeSaRecord);
+ }
+
+ // TODO: b/141275871 Test retransmisstions are fired for correct times within certain time.
+
+ @Test
+ public void testCacheLastRequestAndResponse() throws Exception {
+ setupIdleStateMachine();
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(null /*reqPacket*/);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(null /*respPacketList*/);
+
+ byte[] dummyIkeReqFirstPacket = "testLastSentRequest".getBytes();
+ byte[][] dummyIkeResp =
+ new byte[][] {
+ "testLastSentRespFrag1".getBytes(), "testLastSentRespFrag2".getBytes()
+ };
+
+ when(mMockIkeMessageHelper.encryptAndEncode(
+ any(),
+ any(),
+ eq(mSpyCurrentIkeSaRecord),
+ any(IkeMessage.class),
+ anyBoolean(),
+ anyInt()))
+ .thenReturn(dummyIkeResp);
+
+ // Receive a DPD request, expect to send dummyIkeResp
+ ReceivedIkePacket dummyDpdRequest =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId(), dummyIkeReqFirstPacket);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[1]), eq(REMOTE_ADDRESS));
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ assertTrue(mSpyCurrentIkeSaRecord.isRetransmittedRequest(dummyIkeReqFirstPacket));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testReplyRetransmittedRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock last sent request and response
+ byte[] dummyIkeReqFirstPacket = "testRcvRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testRcvRetransmittedRequestResp".getBytes()};
+
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp));
+ mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
+
+ // Build request with last validated message ID
+ ReceivedIkePacket request =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1,
+ dummyIkeReqFirstPacket);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+
+ mLooper.dispatchAll();
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ verify(mSpyIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testDiscardFakeRetransmittedRequest() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock last sent request and response
+ byte[] dummyIkeReqFirstPacket = "testDiscardFakeRetransmittedRequestReq".getBytes();
+ byte[][] dummyIkeResp = new byte[][] {"testDiscardFakeRetransmittedRequestResp".getBytes()};
+ mSpyCurrentIkeSaRecord.updateLastReceivedReqFirstPacket(dummyIkeReqFirstPacket);
+ mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(Arrays.asList(dummyIkeResp));
+ mSpyCurrentIkeSaRecord.incrementRemoteRequestMessageId();
+
+ // Build request with last validated message ID but different bytes
+ ReceivedIkePacket request =
+ makeDpdIkeRequest(
+ mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1, new byte[0]);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+
+ mLooper.dispatchAll();
+
+ verifyLastSentRespAllPackets(dummyIkeResp, mSpyCurrentIkeSaRecord);
+ verify(mSpyIkeSocket, never()).sendIkePacket(any(), any());
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+
+ @Test
+ public void testDiscardRetransmittedResponse() throws Exception {
+ mockIkeInitAndTransitionToIkeAuth(mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
+ verifyRetransmissionStarted();
+
+ // Build and send fake response with last validated message ID to IKE state machine
+ ReceivedIkePacket resp =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ mSpyCurrentIkeSaRecord.getLocalRequestMessageId() - 1,
+ new LinkedList<>(),
+ new byte[0]);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify current state does not change
+ verifyRetransmissionStarted();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertFalse(ikeHeader.isResponseMsg);
+ assertTrue(ikeHeader.fromIkeInitiator);
+
+ List<IkeDeletePayload> deletePayloadList =
+ delMsg.getPayloadListForType(
+ IkePayload.PAYLOAD_TYPE_DELETE, IkeDeletePayload.class);
+ assertEquals(1, deletePayloadList.size());
+
+ IkeDeletePayload deletePayload = deletePayloadList.get(0);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(0, deletePayload.numSpi);
+ assertEquals(0, deletePayload.spiSize);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteResponse() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ ReceivedIkePacket received = makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
+ mLooper.dispatchAll();
+ verifyIncrementLocaReqMsgId();
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteResponseWithParsingError() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ resetMockIkeMessageHelper();
+
+ // Mock receiving response with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify no more request out
+ verifyEncryptAndEncodeNeverCalled(mSpyCurrentIkeSaRecord);
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteHandlesInvalidResp() throws Exception {
+ setupIdleStateMachine();
+
+ // Send delete request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+
+ // Receive response with wrong exchange type
+ ReceivedIkePacket resp =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ true /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Verify state machine quit properly
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE));
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Verify delete sent out.
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ resetMockIkeMessageHelper(); // Discard value.
+
+ ReceivedIkePacket received = makeRekeyIkeRequest();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received);
+
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+ verifyIncrementRemoteReqMsgId();
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ List<IkeNotifyPayload> notificationPayloadList =
+ resp.getPayloadListForType(IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
+ assertEquals(1, notificationPayloadList.size());
+
+ IkeNotifyPayload notifyPayload = notificationPayloadList.get(0);
+ assertEquals(IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType);
+ }
+
+ @Test
+ public void testDeleteIkeRemoteDelete() throws Exception {
+ setupIdleStateMachine();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
+ makeDeleteIkeRequest(mSpyCurrentIkeSaRecord));
+
+ mLooper.dispatchAll();
+ verifyIncrementRemoteReqMsgId();
+
+ // Verify outbound message
+ IkeMessage delMsg = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+
+ IkeHeader ikeHeader = delMsg.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+
+ assertTrue(delMsg.ikePayloadList.isEmpty());
+
+ verifyNotifyUserCloseSession();
+
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ @Test
+ public void testReceiveDpd() throws Exception {
+ setupIdleStateMachine();
+
+ // Receive a DPD request, expect to stay in IDLE state
+ ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertTrue(resp.ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testReceiveDpdNonIdle() throws Exception {
+ setupIdleStateMachine();
+
+ // Move to a non-idle state. Use RekeyIkeRemoteDelete, as it doesn't send out any requests.
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
+ mLooper.dispatchAll();
+
+ // In a rekey state, receiving (and handling) a DPD should not result in a change of states
+ ReceivedIkePacket dummyDpdRequest = makeDpdIkeRequest(mSpyCurrentIkeSaRecord);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDpdRequest);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeRemoteDelete);
+
+ verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDpdRequest);
+
+ // Verify outbound response
+ IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ IkeHeader ikeHeader = resp.ikeHeader;
+ assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
+ assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
+ assertTrue(ikeHeader.isResponseMsg);
+ assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
+ assertTrue(resp.ikePayloadList.isEmpty());
+ }
+
+ @Test
+ public void testIdleTriggersNewRequests() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Verify that the command is executed, and the state machine transitions to the right state
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyRetransmissionStarted();
+ }
+
+ @Test
+ public void testNonIdleStateDoesNotTriggerNewRequests() throws Exception {
+ setupIdleStateMachine();
+
+ // Force ourselves into a non-idle state
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving);
+ mLooper.dispatchAll();
+ verifyEncryptAndEncodeNeverCalled();
+
+ // Queue a local request, and expect that it is not run (yet)
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
+ new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
+ mLooper.dispatchAll();
+
+ // Verify that the state machine is still in the Receiving state
+ verifyEncryptAndEncodeNeverCalled();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.Receiving);
+
+ // Go back to Idle, and expect to immediately transition to RekeyIkeLocalCreate from the
+ // queued request
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
+ verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
+ }
+
+ @Test
+ public void testOpenChildSessionValidatesArgs() throws Exception {
+ setupIdleStateMachine();
+
+ // Expect failure - no callbacks provided
+ try {
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, null);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Expect failure - callbacks already registered
+ try {
+ mIkeSessionStateMachine.openChildSession(
+ mChildSessionOptions, mMockChildSessionCallback);
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testOpenChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback cb = mock(ChildSessionCallback.class);
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+
+ // Test that inserting the same cb returns an error, even before the state
+ // machine has a chance to process it.
+ try {
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+ } catch (IllegalArgumentException expected) {
+ }
+
+ verify(mMockChildSessionFactoryHelper)
+ .makeChildSessionStateMachine(
+ eq(mLooper.getLooper()),
+ eq(mContext),
+ eq(mChildSessionOptions),
+ eq(mSpyUserCbExecutor),
+ eq(cb),
+ any());
+
+ // Verify state in IkeSessionStateMachine
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+
+ synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
+ assertTrue(mIkeSessionStateMachine.mChildCbToSessions.containsKey(cb));
+ }
+ }
+
+ @Test
+ public void testCloseChildSessionValidatesArgs() throws Exception {
+ setupIdleStateMachine();
+
+ // Expect failure - callbacks not registered
+ try {
+ mIkeSessionStateMachine.closeChildSession(mock(ChildSessionCallback.class));
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testCloseChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ }
+
+ @Test
+ public void testCloseImmediatelyAfterOpenChildSession() throws Exception {
+ setupIdleStateMachine();
+
+ ChildSessionCallback cb = mock(ChildSessionCallback.class);
+ mIkeSessionStateMachine.openChildSession(mChildSessionOptions, cb);
+
+ // Verify that closing the session immediately still picks up the child callback
+ // even before the looper has a chance to run.
+ mIkeSessionStateMachine.closeChildSession(mMockChildSessionCallback);
+ }
+
+ @Test
+ public void testOnChildSessionClosed() throws Exception {
+ setupIdleStateMachine();
+
+ mDummyChildSmCallback.onChildSessionClosed(mMockChildSessionCallback);
+
+ synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
+ assertFalse(
+ mIkeSessionStateMachine.mChildCbToSessions.containsKey(
+ mMockChildSessionCallback));
+ }
+ }
+
+ @Test
+ public void testHandleUnexpectedExceptionInEnterState() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ IkeSessionOptions mockSessionOptions = mock(IkeSessionOptions.class);
+ when(mockSessionOptions.getSaProposals()).thenThrow(mock(RuntimeException.class));
+
+ IkeSessionStateMachine ikeSession =
+ new IkeSessionStateMachine(
+ mLooper.getLooper(),
+ mContext,
+ mIpSecManager,
+ mockSessionOptions,
+ mChildSessionOptions,
+ mSpyUserCbExecutor,
+ mMockIkeSessionCallback,
+ mMockChildSessionCallback,
+ mMockEapAuthenticatorFactory);
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+
+ assertNull(ikeSession.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+
+ @Test
+ public void testHandleUnexpectedExceptionInProcessStateMsg() throws Exception {
+ Log spyIkeLog = TestUtils.makeSpyLogDoLogErrorForWtf(TAG);
+ IkeManager.setIkeLog(spyIkeLog);
+
+ setupIdleStateMachine();
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, null /*receivedIkePacket*/);
+ mLooper.dispatchAll();
+
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mSpyUserCbExecutor).execute(any(Runnable.class));
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
+ }
+
+ @Test
+ public void testCreateIkeLocalIkeInitRcvErrorNotify() throws Exception {
+ // Send IKE INIT request
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
+ mLooper.dispatchAll();
+ verifyRetransmissionStarted();
+
+ // Receive IKE INIT response with erro notification.
+ List<IkePayload> payloads = new LinkedList<>();
+ payloads.add(new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN));
+ ReceivedIkePacket resp =
+ makeDummyUnencryptedReceivedIkePacket(
+ 1L /*initiator SPI*/,
+ 2L /*respodner SPI*/,
+ IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
+ true /*isResp*/,
+ false /*fromIkeInit*/,
+ payloads);
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mLooper.dispatchAll();
+
+ // Fires user error callbacks
+ verify(mMockIkeSessionCallback)
+ .onClosedExceptionally(
+ argThat(err -> err instanceof NoValidProposalChosenException));
+ // Verify state machine quit properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ }
+
+ private void mockSendRekeyChildReq() throws Exception {
+ setupIdleStateMachine();
+
+ ChildLocalRequest childLocalRequest =
+ new ChildLocalRequest(
+ IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
+ mMockChildSessionCallback,
+ null /*childOptions*/);
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, childLocalRequest);
+ mLooper.dispatchAll();
+
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine).rekeyChildSession();
+
+ // Mocking sending request
+ mDummyChildSmCallback.onOutboundPayloadsReady(
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
+ false /*isResp*/,
+ new LinkedList<>(),
+ mMockChildSessionStateMachine);
+ mLooper.dispatchAll();
+ }
+
+ private void mockRcvTempFail() throws Exception {
+ ReceivedIkePacket resp =
+ makeResponseWithErrorNotify(new IkeNotifyPayload(ERROR_TYPE_TEMPORARY_FAILURE));
+
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testTempFailureHandlerScheduleRetry() throws Exception {
+ mockSendRekeyChildReq();
+
+ // Mock sending TEMPORARY_FAILURE response
+ mockRcvTempFail();
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ // Verify that rekey is triggered again
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState()
+ instanceof IkeSessionStateMachine.ChildProcedureOngoing);
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+ }
+
+ @Test
+ public void testTempFailureHandlerTimeout() throws Exception {
+ long currentTime = 0;
+ int retryCnt = 0;
+
+ mockSendRekeyChildReq();
+
+ while (currentTime + RETRY_INTERVAL_MS < TEMP_FAILURE_RETRY_TIMEOUT_MS) {
+ mockRcvTempFail();
+
+ mLooper.moveTimeForward(RETRY_INTERVAL_MS);
+ currentTime += RETRY_INTERVAL_MS;
+ mLooper.dispatchAll();
+
+ retryCnt++;
+ verify(mMockChildSessionStateMachine, times(1 + retryCnt)).rekeyChildSession();
+ }
+
+ mLooper.moveTimeForward(RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(IkeInternalException.class));
+ }
+
+ @Test
+ public void testTempFailureHandlerCancelTimer() throws Exception {
+ mockSendRekeyChildReq();
+
+ // Mock sending TEMPORARY_FAILURE response
+ mockRcvTempFail();
+
+ // Move time forward to trigger retry
+ mLooper.moveTimeForward(IkeSessionStateMachine.RETRY_INTERVAL_MS);
+ mLooper.dispatchAll();
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+
+ // Mock sending a valid response
+ ReceivedIkePacket resp =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ EXCHANGE_TYPE_CREATE_CHILD_SA,
+ true /*isResp*/,
+ new LinkedList<>());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
+ mLooper.dispatchAll();
+
+ // Move time forward
+ mLooper.moveTimeForward(IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS);
+ mLooper.dispatchAll();
+
+ // Validate IKE Session is not closed
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ // Validate no more retry
+ verify(mMockChildSessionStateMachine, times(2)).rekeyChildSession();
+ }
+
+ @Test
+ public void testIdleReceiveRequestWithFatalError() throws Exception {
+ setupIdleStateMachine();
+
+ // Mock receiving packet with syntax error
+ ReceivedIkePacket mockInvalidPacket =
+ makeDummyReceivedIkePacketWithInvalidSyntax(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
+ mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockInvalidPacket);
+ mLooper.dispatchAll();
+
+ // Verify Delete request was sent
+ List<IkePayload> payloads = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, payloads.size());
+
+ IkePayload payload = payloads.get(0);
+ assertEquals(IkePayload.PAYLOAD_TYPE_NOTIFY, payload.payloadType);
+ assertEquals(ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) payload).notifyType);
+
+ // Verify IKE Session is closed properly
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testHandlesInvalidRequest() throws Exception {
+ setupIdleStateMachine();
+
+ mIkeSessionStateMachine.sendMessage(
+ IkeSessionStateMachine.CMD_FORCE_TRANSITION,
+ mIkeSessionStateMachine.mChildProcedureOngoing);
+
+ // Receive an IKE AUTH request
+ ReceivedIkePacket request =
+ makeDummyEncryptedReceivedIkePacketWithPayloadList(
+ mSpyCurrentIkeSaRecord,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ false /*isResp*/,
+ new LinkedList<IkePayload>());
+ mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
+ mLooper.dispatchAll();
+
+ // Verify error notification was sent
+ List<IkePayload> ikePayloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/);
+ assertEquals(1, ikePayloadList.size());
+ assertEquals(
+ ERROR_TYPE_INVALID_SYNTAX, ((IkeNotifyPayload) ikePayloadList.get(0)).notifyType);
+
+ // Verify IKE Session has quit
+ assertNull(mIkeSessionStateMachine.getCurrentState());
+ verify(mMockIkeSessionCallback).onClosedExceptionally(any(InvalidSyntaxException.class));
+ }
+
+ @Test
+ public void testIdleHandlesUnprotectedPacket() throws Exception {
+ setupIdleStateMachine();
+
+ ReceivedIkePacket req =
+ makeDummyReceivedIkePacketWithUnprotectedError(
+ mSpyCurrentIkeSaRecord,
+ false /*isResp*/,
+ EXCHANGE_TYPE_INFORMATIONAL,
+ mock(IkeException.class));
+
+ mLooper.dispatchAll();
+ assertTrue(
+ mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java
new file mode 100644
index 00000000..1b8761f7
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSocketTest.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.IkeSocket.PacketReceiver;
+import com.android.internal.net.ipsec.ike.message.IkeHeader;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class IkeSocketTest {
+ private static final int REMOTE_RECV_BUFF_SIZE = 2048;
+ private static final int TIMEOUT = 1000;
+
+ private static final String NON_ESP_MARKER_HEX_STRING = "00000000";
+ private static final String IKE_REQ_MESSAGE_HEX_STRING =
+ "5f54bf6d8b48e6e100000000000000002120220800000000"
+ + "00000150220000300000002c010100040300000c0100000c"
+ + "800e00800300000803000002030000080400000200000008"
+ + "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ + "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ + "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ + "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
+
+ private static final String LOCAL_SPI = "0000000000000000";
+ private static final String REMOTE_SPI = "5f54bf6d8b48e6e1";
+
+ private static final String DATA_ONE = "one 1";
+ private static final String DATA_TWO = "two 2";
+
+ private static final String IPV4_LOOPBACK = "127.0.0.1";
+
+ private byte[] mDataOne;
+ private byte[] mDataTwo;
+
+ private long mLocalSpi;
+ private long mRemoteSpi;
+
+ private LongSparseArray mSpiToIkeStateMachineMap;
+ private PacketReceiver mPacketReceiver;
+
+ private UdpEncapsulationSocket mClientUdpEncapSocket;
+ private InetAddress mLocalAddress;
+ private FileDescriptor mDummyRemoteServerFd;
+
+ private IkeSessionStateMachine mMockIkeSessionStateMachine;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE);
+ mClientUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket();
+
+ mLocalAddress = InetAddress.getByName(IPV4_LOOPBACK);
+ mDummyRemoteServerFd = getBoundUdpSocket(mLocalAddress);
+
+ mDataOne = DATA_ONE.getBytes("UTF-8");
+ mDataTwo = DATA_TWO.getBytes("UTF-8");
+
+ ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI));
+ mLocalSpi = localSpiBuffer.getLong();
+ ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI));
+ mRemoteSpi = remoteSpiBuffer.getLong();
+
+ mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class);
+
+ mSpiToIkeStateMachineMap = new LongSparseArray<IkeSessionStateMachine>();
+ mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine);
+
+ mPacketReceiver = new IkeSocket.PacketReceiver();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mClientUdpEncapSocket.close();
+ IkeSocket.setPacketReceiver(mPacketReceiver);
+ Os.close(mDummyRemoteServerFd);
+ }
+
+ private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception {
+ FileDescriptor sock =
+ Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP);
+ Os.bind(sock, address, IkeSocket.IKE_SERVER_PORT);
+ return sock;
+ }
+
+ @Test
+ public void testGetAndCloseIkeSocket() throws Exception {
+ // Must be prepared here; AndroidJUnitRunner runs tests on different threads from the
+ // setUp() call. Since the new Handler() call is run in getIkeSocket, the Looper must be
+ // prepared here.
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ IkeSessionStateMachine mMockIkeSessionOne = mock(IkeSessionStateMachine.class);
+ IkeSessionStateMachine mMockIkeSessionTwo = mock(IkeSessionStateMachine.class);
+
+ IkeSocket ikeSocketOne = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionOne);
+ assertEquals(1, ikeSocketOne.mAliveIkeSessions.size());
+
+ IkeSocket ikeSocketTwo = IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionTwo);
+ assertEquals(ikeSocketOne, ikeSocketTwo);
+ assertEquals(2, ikeSocketTwo.mAliveIkeSessions.size());
+
+ ikeSocketOne.releaseReference(mMockIkeSessionOne);
+ assertEquals(1, ikeSocketOne.mAliveIkeSessions.size());
+
+ ikeSocketTwo.releaseReference(mMockIkeSessionTwo);
+ assertEquals(0, ikeSocketTwo.mAliveIkeSessions.size());
+ }
+
+ @Test
+ public void testSendIkePacket() throws Exception {
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ // Send IKE packet
+ IkeSocket ikeSocket =
+ IkeSocket.getIkeSocket(mClientUdpEncapSocket, mMockIkeSessionStateMachine);
+ ikeSocket.sendIkePacket(mDataOne, mLocalAddress);
+
+ byte[] receivedData = receive(mDummyRemoteServerFd);
+
+ // Verify received data
+ ByteBuffer expectedBuffer =
+ ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length);
+ expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne);
+
+ assertArrayEquals(expectedBuffer.array(), receivedData);
+
+ ikeSocket.releaseReference(mMockIkeSessionStateMachine);
+ }
+
+ @Test
+ public void testReceiveIkePacket() throws Exception {
+ // Create working thread.
+ HandlerThread mIkeThread = new HandlerThread("IkeSocketTest");
+ mIkeThread.start();
+
+ // Create IkeSocket on working thread.
+ IkeSocketReceiver socketReceiver = new IkeSocketReceiver();
+ TestCountDownLatch createLatch = new TestCountDownLatch();
+ mIkeThread
+ .getThreadHandler()
+ .post(
+ () -> {
+ try {
+ socketReceiver.setIkeSocket(
+ IkeSocket.getIkeSocket(
+ mClientUdpEncapSocket,
+ mMockIkeSessionStateMachine));
+ createLatch.countDown();
+ Log.d("IkeSocketTest", "IkeSocket created.");
+ } catch (ErrnoException e) {
+ Log.e("IkeSocketTest", "error encountered creating IkeSocket ", e);
+ }
+ });
+ createLatch.await();
+
+ IkeSocket ikeSocket = socketReceiver.getIkeSocket();
+ assertNotNull(ikeSocket);
+
+ // Configure IkeSocket
+ TestCountDownLatch receiveLatch = new TestCountDownLatch();
+ DummyPacketReceiver packetReceiver = new DummyPacketReceiver(receiveLatch);
+ IkeSocket.setPacketReceiver(packetReceiver);
+
+ // Send first packet.
+ sendToIkeSocket(mDummyRemoteServerFd, mDataOne, mLocalAddress);
+ receiveLatch.await();
+
+ assertEquals(1, ikeSocket.numPacketsReceived());
+ assertArrayEquals(mDataOne, packetReceiver.mReceivedData);
+
+ // Send second packet.
+ sendToIkeSocket(mDummyRemoteServerFd, mDataTwo, mLocalAddress);
+ receiveLatch.await();
+
+ assertEquals(2, ikeSocket.numPacketsReceived());
+ assertArrayEquals(mDataTwo, packetReceiver.mReceivedData);
+
+ // Close IkeSocket.
+ TestCountDownLatch closeLatch = new TestCountDownLatch();
+ ikeSocket
+ .getHandler()
+ .post(
+ () -> {
+ ikeSocket.releaseReference(mMockIkeSessionStateMachine);
+ closeLatch.countDown();
+ });
+ closeLatch.await();
+
+ mIkeThread.quitSafely();
+ }
+
+ @Test
+ public void testHandlePacket() throws Exception {
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING);
+ ArgumentCaptor<IkeHeader> ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class);
+ verify(mMockIkeSessionStateMachine)
+ .receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes));
+
+ IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue();
+ assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi);
+ assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi);
+ }
+
+ @Test
+ public void testHandleEspPacket() throws Exception {
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
+ // Modify Non-ESP Marker
+ recvBuf[0] = 1;
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
+ }
+
+ @Test
+ public void testHandlePacketWithMalformedHeader() throws Exception {
+ String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000";
+ byte[] recvBuf =
+ TestUtils.hexStringToByteArray(
+ NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString);
+
+ mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
+
+ verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
+ }
+
+ private byte[] receive(FileDescriptor mfd) throws Exception {
+ byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE];
+ AtomicInteger bytesRead = new AtomicInteger(-1);
+ Thread receiveThread =
+ new Thread(
+ () -> {
+ while (bytesRead.get() < 0) {
+ try {
+ bytesRead.set(
+ Os.recvfrom(
+ mDummyRemoteServerFd,
+ receiveBuffer,
+ 0,
+ REMOTE_RECV_BUFF_SIZE,
+ 0,
+ null));
+ } catch (Exception e) {
+ Log.e(
+ "IkeSocketTest",
+ "Error encountered reading from socket",
+ e);
+ }
+ }
+ Log.d(
+ "IkeSocketTest",
+ "Packet received with size of " + bytesRead.get());
+ });
+
+ receiveThread.start();
+ receiveThread.join(TIMEOUT);
+
+ return Arrays.copyOfRange(receiveBuffer, 0, bytesRead.get());
+ }
+
+ private void sendToIkeSocket(FileDescriptor fd, byte[] data, InetAddress destAddress)
+ throws Exception {
+ Os.sendto(fd, data, 0, data.length, 0, destAddress, mClientUdpEncapSocket.getPort());
+ }
+
+ private static class IkeSocketReceiver {
+ private IkeSocket mIkeSocket;
+
+ void setIkeSocket(IkeSocket ikeSocket) {
+ mIkeSocket = ikeSocket;
+ }
+
+ IkeSocket getIkeSocket() {
+ return mIkeSocket;
+ }
+ }
+
+ private static class DummyPacketReceiver implements IkeSocket.IPacketReceiver {
+ byte[] mReceivedData = null;
+ final TestCountDownLatch mLatch;
+
+ DummyPacketReceiver(TestCountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ public void handlePacket(
+ byte[] revbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
+ mReceivedData = Arrays.copyOfRange(revbuf, 0, revbuf.length);
+ mLatch.countDown();
+ Log.d("IkeSocketTest", "Packet received");
+ }
+ }
+
+ private static class TestCountDownLatch {
+ private CountDownLatch mLatch;
+
+ TestCountDownLatch() {
+ reset();
+ }
+
+ private void reset() {
+ mLatch = new CountDownLatch(1);
+ }
+
+ void countDown() {
+ mLatch.countDown();
+ }
+
+ void await() {
+ try {
+ if (!mLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
+ fail("Time out");
+ }
+ } catch (InterruptedException e) {
+ fail(e.toString());
+ }
+ reset();
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java
new file mode 100644
index 00000000..646b9d9a
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/SaRecordTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2019 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;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.aryEq;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecManager.SecurityParameterIndex;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.IpSecTransform;
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.ChildLocalRequest;
+import com.android.internal.net.ipsec.ike.IkeLocalRequestScheduler.LocalRequest;
+import com.android.internal.net.ipsec.ike.IkeSessionStateMachine.IkeSecurityParameterIndex;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.ChildSaRecordConfig;
+import com.android.internal.net.ipsec.ike.SaRecord.IIpSecTransformHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecordConfig;
+import com.android.internal.net.ipsec.ike.SaRecord.IpSecTransformHelper;
+import com.android.internal.net.ipsec.ike.SaRecord.SaRecordHelper;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.server.IpSecService;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.Inet4Address;
+
+@RunWith(JUnit4.class)
+public final class SaRecordTest {
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+
+ private static final String PRF_KEY_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String DATA_TO_SIGN_HEX_STRING = "010000000a50500d";
+ private static final String CALCULATED_MAC_HEX_STRING =
+ "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A";
+
+ private static final long IKE_INIT_SPI = 0x5F54BF6D8B48E6E1L;
+ private static final long IKE_RESP_SPI = 0x909232B3D1EDCB5CL;
+
+ private static final String IKE_NONCE_INIT_HEX_STRING =
+ "C39B7F368F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C72CB4240EB5C46412";
+ private static final String IKE_NONCE_RESP_HEX_STRING =
+ "9756112CA539F5C25ABACC7EE92B73091942A9C06950F98848F1AF1694C4DDFF";
+
+ private static final String IKE_SHARED_DH_KEY_HEX_STRING =
+ "C14155DEA40056BD9C76FB4819687B7A397582F4CD5AFF4B"
+ + "8F441C56E0C08C84234147A0BA249A555835A048E3CA2980"
+ + "7D057A61DD26EEFAD9AF9C01497005E52858E29FB42EB849"
+ + "6731DF96A11CCE1F51137A9A1B900FA81AEE7898E373D4E4"
+ + "8B899BBECA091314ECD4B6E412EF4B0FEF798F54735F3180"
+ + "7424A318287F20E8";
+
+ private static final String IKE_SKEYSEED_HEX_STRING =
+ "8C42F3B1F5F81C7BAAC5F33E9A4F01987B2F9657";
+ private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6";
+ private static final String IKE_SK_AUTH_INIT_HEX_STRING =
+ "554FBF5A05B7F511E05A30CE23D874DB9EF55E51";
+ private static final String IKE_SK_AUTH_RESP_HEX_STRING =
+ "36D83420788337CA32ECAA46892C48808DCD58B1";
+ private static final String IKE_SK_ENCR_INIT_HEX_STRING = "5CBFD33F75796C0188C4A3A546AEC4A1";
+ private static final String IKE_SK_ENCR_RESP_HEX_STRING = "C33B35FCF29514CD9D8B4A695E1A816E";
+ private static final String IKE_SK_PRF_INIT_HEX_STRING =
+ "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String IKE_SK_PRF_RESP_HEX_STRING =
+ "A30E6B08BE56C0E6BFF4744143C75219299E1BEB";
+ private static final String IKE_KEY_MAT =
+ IKE_SK_D_HEX_STRING
+ + IKE_SK_AUTH_INIT_HEX_STRING
+ + IKE_SK_AUTH_RESP_HEX_STRING
+ + IKE_SK_ENCR_INIT_HEX_STRING
+ + IKE_SK_ENCR_RESP_HEX_STRING
+ + IKE_SK_PRF_INIT_HEX_STRING
+ + IKE_SK_PRF_RESP_HEX_STRING;
+
+ private static final int IKE_AUTH_ALGO_KEY_LEN = 20;
+ private static final int IKE_ENCR_ALGO_KEY_LEN = 16;
+ private static final int IKE_PRF_KEY_LEN = 20;
+ private static final int IKE_SK_D_KEY_LEN = IKE_PRF_KEY_LEN;
+
+ private static final int FIRST_CHILD_INIT_SPI = 0x2ad4c0a2;
+ private static final int FIRST_CHILD_RESP_SPI = 0xcae7019f;
+
+ private static final String FIRST_CHILD_ENCR_INIT_HEX_STRING =
+ "1B865CEA6E2C23973E8C5452ADC5CD7D";
+ private static final String FIRST_CHILD_ENCR_RESP_HEX_STRING =
+ "5E82FEDACC6DCB0756DDD7553907EBD1";
+ private static final String FIRST_CHILD_AUTH_INIT_HEX_STRING =
+ "A7A5A44F7EF4409657206C7DC52B7E692593B51E";
+ private static final String FIRST_CHILD_AUTH_RESP_HEX_STRING =
+ "CDE612189FD46DE870FAEC04F92B40B0BFDBD9E1";
+ private static final String FIRST_CHILD_KEY_MAT =
+ FIRST_CHILD_ENCR_INIT_HEX_STRING
+ + FIRST_CHILD_AUTH_INIT_HEX_STRING
+ + FIRST_CHILD_ENCR_RESP_HEX_STRING
+ + FIRST_CHILD_AUTH_RESP_HEX_STRING;
+
+ private static final int FIRST_CHILD_AUTH_ALGO_KEY_LEN = 20;
+ private static final int FIRST_CHILD_ENCR_ALGO_KEY_LEN = 16;
+
+ private IkeMacPrf mIkeHmacSha1Prf;
+ private IkeMacIntegrity mHmacSha1IntegrityMac;
+ private IkeCipher mAesCbcCipher;
+
+ private LocalRequest mMockFutureRekeyIkeEvent;
+ private ChildLocalRequest mMockFutureRekeyChildEvent;
+
+ private SaRecordHelper mSaRecordHelper = new SaRecordHelper();
+
+ @Before
+ public void setUp() throws Exception {
+ mIkeHmacSha1Prf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+ mHmacSha1IntegrityMac =
+ IkeMacIntegrity.create(
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
+ IkeMessage.getSecurityProvider());
+ mAesCbcCipher =
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
+ SaProposal.KEY_LEN_AES_128),
+ IkeMessage.getSecurityProvider());
+
+ mMockFutureRekeyIkeEvent = mock(LocalRequest.class);
+ mMockFutureRekeyChildEvent = mock(ChildLocalRequest.class);
+ }
+
+ // Test generating keying material for making IKE SA.
+ @Test
+ public void testMakeIkeSaRecord() throws Exception {
+ byte[] sKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
+
+ IkeSecurityParameterIndex ikeInitSpi =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(
+ LOCAL_ADDRESS, IKE_INIT_SPI);
+ IkeSecurityParameterIndex ikeRespSpi =
+ IkeSecurityParameterIndex.allocateSecurityParameterIndex(
+ REMOTE_ADDRESS, IKE_RESP_SPI);
+ IkeSaRecordConfig ikeSaRecordConfig =
+ new IkeSaRecordConfig(
+ ikeInitSpi,
+ ikeRespSpi,
+ mIkeHmacSha1Prf,
+ IKE_AUTH_ALGO_KEY_LEN,
+ IKE_ENCR_ALGO_KEY_LEN,
+ true /*isLocalInit*/,
+ mMockFutureRekeyIkeEvent);
+
+ int keyMaterialLen =
+ IKE_SK_D_KEY_LEN
+ + IKE_AUTH_ALGO_KEY_LEN * 2
+ + IKE_ENCR_ALGO_KEY_LEN * 2
+ + IKE_PRF_KEY_LEN * 2;
+
+ IkeSaRecord ikeSaRecord =
+ mSaRecordHelper.makeIkeSaRecord(sKeySeed, nonceInit, nonceResp, ikeSaRecordConfig);
+
+ assertTrue(ikeSaRecord.isLocalInit);
+ assertEquals(IKE_INIT_SPI, ikeSaRecord.getInitiatorSpi());
+ assertEquals(IKE_RESP_SPI, ikeSaRecord.getResponderSpi());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING), ikeSaRecord.getSkD());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_AUTH_INIT_HEX_STRING),
+ ikeSaRecord.getOutboundIntegrityKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_AUTH_RESP_HEX_STRING),
+ ikeSaRecord.getInboundIntegrityKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_ENCR_INIT_HEX_STRING),
+ ikeSaRecord.getOutboundEncryptionKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_ENCR_RESP_HEX_STRING),
+ ikeSaRecord.getInboundDecryptionKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_PRF_INIT_HEX_STRING), ikeSaRecord.getSkPi());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(IKE_SK_PRF_RESP_HEX_STRING), ikeSaRecord.getSkPr());
+
+ ikeSaRecord.close();
+
+ verify(mMockFutureRekeyIkeEvent).cancel();
+ }
+
+ // Test generating keying material and building IpSecTransform for making Child SA.
+ @Test
+ public void testMakeChildSaRecord() throws Exception {
+ byte[] sharedKey = new byte[0];
+ byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
+
+ MockIpSecTestUtils mockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec();
+ IpSecManager ipSecManager = mockIpSecTestUtils.getIpSecManager();
+ IpSecService mockIpSecService = mockIpSecTestUtils.getIpSecService();
+ Context context = mockIpSecTestUtils.getContext();
+
+ when(mockIpSecService.allocateSecurityParameterIndex(
+ eq(LOCAL_ADDRESS.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(FIRST_CHILD_INIT_SPI));
+ when(mockIpSecService.allocateSecurityParameterIndex(
+ eq(REMOTE_ADDRESS.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(FIRST_CHILD_RESP_SPI));
+
+ SecurityParameterIndex childInitSpi =
+ ipSecManager.allocateSecurityParameterIndex(LOCAL_ADDRESS);
+ SecurityParameterIndex childRespSpi =
+ ipSecManager.allocateSecurityParameterIndex(REMOTE_ADDRESS);
+
+ byte[] initAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_INIT_HEX_STRING);
+ byte[] respAuthKey = TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_RESP_HEX_STRING);
+ byte[] initEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING);
+ byte[] respEncryptionKey = TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING);
+
+ IIpSecTransformHelper mockIpSecHelper;
+ mockIpSecHelper = mock(IIpSecTransformHelper.class);
+ SaRecord.setIpSecTransformHelper(mockIpSecHelper);
+
+ IpSecTransform mockInTransform = mock(IpSecTransform.class);
+ IpSecTransform mockOutTransform = mock(IpSecTransform.class);
+ UdpEncapsulationSocket mockUdpEncapSocket = mock(UdpEncapsulationSocket.class);
+
+ when(mockIpSecHelper.makeIpSecTransform(
+ eq(context),
+ eq(LOCAL_ADDRESS),
+ eq(mockUdpEncapSocket),
+ eq(childRespSpi),
+ eq(mHmacSha1IntegrityMac),
+ eq(mAesCbcCipher),
+ aryEq(initAuthKey),
+ aryEq(initEncryptionKey),
+ eq(false)))
+ .thenReturn(mockOutTransform);
+
+ when(mockIpSecHelper.makeIpSecTransform(
+ eq(context),
+ eq(REMOTE_ADDRESS),
+ eq(mockUdpEncapSocket),
+ eq(childInitSpi),
+ eq(mHmacSha1IntegrityMac),
+ eq(mAesCbcCipher),
+ aryEq(respAuthKey),
+ aryEq(respEncryptionKey),
+ eq(false)))
+ .thenReturn(mockInTransform);
+
+ ChildSaRecordConfig childSaRecordConfig =
+ new ChildSaRecordConfig(
+ mockIpSecTestUtils.getContext(),
+ childInitSpi,
+ childRespSpi,
+ LOCAL_ADDRESS,
+ REMOTE_ADDRESS,
+ mockUdpEncapSocket,
+ mIkeHmacSha1Prf,
+ mHmacSha1IntegrityMac,
+ mAesCbcCipher,
+ TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING),
+ false /*isTransport*/,
+ true /*isLocalInit*/,
+ mMockFutureRekeyChildEvent);
+
+ ChildSaRecord childSaRecord =
+ mSaRecordHelper.makeChildSaRecord(
+ sharedKey, nonceInit, nonceResp, childSaRecordConfig);
+
+ assertTrue(childSaRecord.isLocalInit);
+ assertEquals(FIRST_CHILD_INIT_SPI, childSaRecord.getLocalSpi());
+ assertEquals(FIRST_CHILD_RESP_SPI, childSaRecord.getRemoteSpi());
+ assertEquals(mockInTransform, childSaRecord.getInboundIpSecTransform());
+ assertEquals(mockOutTransform, childSaRecord.getOutboundIpSecTransform());
+
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_INIT_HEX_STRING),
+ childSaRecord.getOutboundIntegrityKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(FIRST_CHILD_AUTH_RESP_HEX_STRING),
+ childSaRecord.getInboundIntegrityKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_INIT_HEX_STRING),
+ childSaRecord.getOutboundEncryptionKey());
+ assertArrayEquals(
+ TestUtils.hexStringToByteArray(FIRST_CHILD_ENCR_RESP_HEX_STRING),
+ childSaRecord.getInboundDecryptionKey());
+
+ childSaRecord.close();
+ verify(mMockFutureRekeyChildEvent).cancel();
+
+ SaRecord.setIpSecTransformHelper(new IpSecTransformHelper());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java
new file mode 100644
index 00000000..a3b2253e
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeCombinedModeCipherTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2019 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.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.AEADBadTagException;
+
+@RunWith(JUnit4.class)
+public final class IkeCombinedModeCipherTest {
+ private static final String IV = "fbd69d9de2dafc5e";
+ private static final String ENCRYPTED_PADDED_DATA_WITH_CHECKSUM =
+ "f4109834e9f3559758c05edf119917521b885f67f0d14ced43";
+ private static final String UNENCRYPTED_PADDED_DATA = "000000080000400f00";
+ private static final String ADDITIONAL_AUTH_DATA =
+ "77c708b4523e39a471dc683c1d4f21362e202508000000060000004129000025";
+ private static final String KEY =
+ "7C04513660DEC572D896105254EF92608054F8E6EE19E79CE52AB8697B2B5F2C2AA90C29";
+
+ private static final int AES_GCM_IV_LEN = 8;
+ private static final int AES_GCM_16_CHECKSUM_LEN = 128;
+
+ private IkeCombinedModeCipher mAesGcm16Cipher;
+
+ private byte[] mAesGcmKey;
+ private byte[] mIv;
+ private byte[] mEncryptedPaddedDataWithChecksum;
+ private byte[] mUnencryptedPaddedData;
+ private byte[] mAdditionalAuthData;
+
+ @Before
+ public void setUp() {
+ mAesGcm16Cipher =
+ (IkeCombinedModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16,
+ SaProposal.KEY_LEN_AES_256),
+ IkeMessage.getSecurityProvider());
+
+ mAesGcmKey = TestUtils.hexStringToByteArray(KEY);
+ mIv = TestUtils.hexStringToByteArray(IV);
+ mEncryptedPaddedDataWithChecksum =
+ TestUtils.hexStringToByteArray(ENCRYPTED_PADDED_DATA_WITH_CHECKSUM);
+ mUnencryptedPaddedData = TestUtils.hexStringToByteArray(UNENCRYPTED_PADDED_DATA);
+ mAdditionalAuthData = TestUtils.hexStringToByteArray(ADDITIONAL_AUTH_DATA);
+ }
+
+ @Test
+ public void testBuild() throws Exception {
+ assertTrue(mAesGcm16Cipher.isAead());
+ assertEquals(AES_GCM_IV_LEN, mAesGcm16Cipher.generateIv().length);
+ }
+
+ @Test
+ public void testGenerateRandomIv() throws Exception {
+ assertFalse(Arrays.equals(mAesGcm16Cipher.generateIv(), mAesGcm16Cipher.generateIv()));
+ }
+
+ @Test
+ public void testEncrypt() throws Exception {
+ byte[] calculatedData =
+ mAesGcm16Cipher.encrypt(
+ mUnencryptedPaddedData, mAdditionalAuthData, mAesGcmKey, mIv);
+
+ assertArrayEquals(mEncryptedPaddedDataWithChecksum, calculatedData);
+ }
+
+ @Test
+ public void testDecrypt() throws Exception {
+ byte[] calculatedData =
+ mAesGcm16Cipher.decrypt(
+ mEncryptedPaddedDataWithChecksum, mAdditionalAuthData, mAesGcmKey, mIv);
+
+ assertArrayEquals(mUnencryptedPaddedData, calculatedData);
+ }
+
+ @Test
+ public void testEncryptWithWrongKeyLen() throws Exception {
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(KEY + "00");
+
+ try {
+ mAesGcm16Cipher.encrypt(
+ mUnencryptedPaddedData, mAdditionalAuthData, encryptionKey, mIv);
+ fail("Expected to fail because encryption key has wrong length.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecrypWithWrongKey() throws Exception {
+ byte[] encryptionKey = new byte[mAesGcmKey.length];
+ new Random().nextBytes(encryptionKey);
+
+ try {
+ mAesGcm16Cipher.decrypt(
+ mEncryptedPaddedDataWithChecksum, mAdditionalAuthData, encryptionKey, mIv);
+ fail("Expected to fail because decryption key is wrong");
+ } catch (AEADBadTagException expected) {
+
+ }
+ }
+
+ @Test
+ public void testBuildIpSecAlgorithm() throws Exception {
+ IpSecAlgorithm ipsecAlgorithm = mAesGcm16Cipher.buildIpSecAlgorithmWithKey(mAesGcmKey);
+
+ IpSecAlgorithm expectedIpSecAlgorithm =
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM, mAesGcmKey, AES_GCM_16_CHECKSUM_LEN);
+
+ assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm));
+ }
+
+ @Test
+ public void buildIpSecAlgorithmWithInvalidKey() throws Exception {
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(KEY + "00");
+
+ try {
+ mAesGcm16Cipher.buildIpSecAlgorithmWithKey(encryptionKey);
+ fail("Expected to fail because encryption key has wrong length.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java
new file mode 100644
index 00000000..ed625660
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacIntegrityTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public final class IkeMacIntegrityTest {
+ private static final String DATA_TO_AUTH_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec"
+ + "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c"
+ + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ + "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ + "59eb4e81";
+ private static final String INTEGRITY_KEY_HEX_STRING =
+ "554fbf5a05b7f511e05a30ce23d874db9ef55e51";
+ private static final String CHECKSUM_HEX_STRING = "ae6e0f22abdad69ba8007d50";
+
+ private IkeMacIntegrity mHmacSha1IntegrityMac;
+ private byte[] mHmacSha1IntegrityKey;
+
+ private byte[] mDataToAuthenticate;
+
+ @Before
+ public void setUp() throws Exception {
+ mHmacSha1IntegrityMac =
+ IkeMacIntegrity.create(
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
+ IkeMessage.getSecurityProvider());
+ mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING);
+
+ mDataToAuthenticate = TestUtils.hexStringToByteArray(DATA_TO_AUTH_HEX_STRING);
+ }
+
+ @Test
+ public void testGenerateChecksum() throws Exception {
+ byte[] calculatedChecksum =
+ mHmacSha1IntegrityMac.generateChecksum(mHmacSha1IntegrityKey, mDataToAuthenticate);
+
+ byte[] expectedChecksum = TestUtils.hexStringToByteArray(CHECKSUM_HEX_STRING);
+ assertArrayEquals(expectedChecksum, calculatedChecksum);
+ }
+
+ @Test
+ public void testGenerateChecksumWithDifferentKey() throws Exception {
+ byte[] integrityKey = mHmacSha1IntegrityKey.clone();
+ integrityKey[0]++;
+
+ byte[] calculatedChecksum =
+ mHmacSha1IntegrityMac.generateChecksum(integrityKey, mDataToAuthenticate);
+
+ byte[] expectedChecksum = TestUtils.hexStringToByteArray(CHECKSUM_HEX_STRING);
+ assertFalse(Arrays.equals(expectedChecksum, calculatedChecksum));
+ }
+
+ @Test
+ public void testGenerateChecksumWithInvalidKey() throws Exception {
+ byte[] integrityKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "0000");
+
+ try {
+ byte[] calculatedChecksum =
+ mHmacSha1IntegrityMac.generateChecksum(integrityKey, mDataToAuthenticate);
+ fail("Expected to fail due to invalid authentication key.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testBuildIpSecAlgorithm() throws Exception {
+ IpSecAlgorithm ipsecAlgorithm =
+ mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(mHmacSha1IntegrityKey);
+
+ IpSecAlgorithm expectedIpSecAlgorithm =
+ new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, mHmacSha1IntegrityKey, 96);
+
+ assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm));
+ }
+
+ @Test
+ public void buildIpSecAlgorithmWithInvalidKey() throws Exception {
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(INTEGRITY_KEY_HEX_STRING + "00");
+
+ try {
+ mHmacSha1IntegrityMac.buildIpSecAlgorithmWithKey(encryptionKey);
+
+ fail("Expected to fail due to integrity key with wrong length.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java
new file mode 100644
index 00000000..717886f7
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeMacPrfTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 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.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public final class IkeMacPrfTest {
+
+ private static final String PRF_KEY_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String DATA_TO_SIGN_HEX_STRING = "010000000a50500d";
+ private static final String CALCULATED_MAC_HEX_STRING =
+ "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A";
+
+ private static final String IKE_INIT_SPI = "5F54BF6D8B48E6E1";
+ private static final String IKE_RESP_SPI = "909232B3D1EDCB5C";
+
+ private static final String IKE_NONCE_INIT_HEX_STRING =
+ "C39B7F368F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C72CB4240EB5C46412";
+ private static final String IKE_NONCE_RESP_HEX_STRING =
+ "9756112CA539F5C25ABACC7EE92B73091942A9C06950F98848F1AF1694C4DDFF";
+
+ private static final String IKE_SHARED_DH_KEY_HEX_STRING =
+ "C14155DEA40056BD9C76FB4819687B7A397582F4CD5AFF4B"
+ + "8F441C56E0C08C84234147A0BA249A555835A048E3CA2980"
+ + "7D057A61DD26EEFAD9AF9C01497005E52858E29FB42EB849"
+ + "6731DF96A11CCE1F51137A9A1B900FA81AEE7898E373D4E4"
+ + "8B899BBECA091314ECD4B6E412EF4B0FEF798F54735F3180"
+ + "7424A318287F20E8";
+
+ private static final String IKE_SKEYSEED_HEX_STRING =
+ "8C42F3B1F5F81C7BAAC5F33E9A4F01987B2F9657";
+ private static final String IKE_SK_D_HEX_STRING = "C86B56EFCF684DCC2877578AEF3137167FE0EBF6";
+ private static final String IKE_SK_AUTH_INIT_HEX_STRING =
+ "554FBF5A05B7F511E05A30CE23D874DB9EF55E51";
+ private static final String IKE_SK_AUTH_RESP_HEX_STRING =
+ "36D83420788337CA32ECAA46892C48808DCD58B1";
+ private static final String IKE_SK_ENCR_INIT_HEX_STRING = "5CBFD33F75796C0188C4A3A546AEC4A1";
+ private static final String IKE_SK_ENCR_RESP_HEX_STRING = "C33B35FCF29514CD9D8B4A695E1A816E";
+ private static final String IKE_SK_PRF_INIT_HEX_STRING =
+ "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String IKE_SK_PRF_RESP_HEX_STRING =
+ "A30E6B08BE56C0E6BFF4744143C75219299E1BEB";
+ private static final String IKE_KEY_MAT =
+ IKE_SK_D_HEX_STRING
+ + IKE_SK_AUTH_INIT_HEX_STRING
+ + IKE_SK_AUTH_RESP_HEX_STRING
+ + IKE_SK_ENCR_INIT_HEX_STRING
+ + IKE_SK_ENCR_RESP_HEX_STRING
+ + IKE_SK_PRF_INIT_HEX_STRING
+ + IKE_SK_PRF_RESP_HEX_STRING;
+
+ private static final int IKE_AUTH_ALGO_KEY_LEN = 20;
+ private static final int IKE_ENCR_ALGO_KEY_LEN = 16;
+ private static final int IKE_PRF_KEY_LEN = 20;
+ private static final int IKE_SK_D_KEY_LEN = IKE_PRF_KEY_LEN;
+
+ private static final String FIRST_CHILD_ENCR_INIT_HEX_STRING =
+ "1B865CEA6E2C23973E8C5452ADC5CD7D";
+ private static final String FIRST_CHILD_ENCR_RESP_HEX_STRING =
+ "5E82FEDACC6DCB0756DDD7553907EBD1";
+ private static final String FIRST_CHILD_AUTH_INIT_HEX_STRING =
+ "A7A5A44F7EF4409657206C7DC52B7E692593B51E";
+ private static final String FIRST_CHILD_AUTH_RESP_HEX_STRING =
+ "CDE612189FD46DE870FAEC04F92B40B0BFDBD9E1";
+ private static final String FIRST_CHILD_KEY_MAT =
+ FIRST_CHILD_ENCR_INIT_HEX_STRING
+ + FIRST_CHILD_AUTH_INIT_HEX_STRING
+ + FIRST_CHILD_ENCR_RESP_HEX_STRING
+ + FIRST_CHILD_AUTH_RESP_HEX_STRING;
+
+ private static final int FIRST_CHILD_AUTH_ALGO_KEY_LEN = 20;
+ private static final int FIRST_CHILD_ENCR_ALGO_KEY_LEN = 16;
+
+ private IkeMacPrf mIkeHmacSha1Prf;
+
+ @Before
+ public void setUp() throws Exception {
+ mIkeHmacSha1Prf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+ }
+
+ @Test
+ public void testsignBytes() throws Exception {
+ byte[] skpBytes = TestUtils.hexStringToByteArray(PRF_KEY_HEX_STRING);
+ byte[] dataBytes = TestUtils.hexStringToByteArray(DATA_TO_SIGN_HEX_STRING);
+
+ byte[] calculatedBytes = mIkeHmacSha1Prf.signBytes(skpBytes, dataBytes);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(CALCULATED_MAC_HEX_STRING);
+ assertArrayEquals(expectedBytes, calculatedBytes);
+ }
+
+ @Test
+ public void testGenerateSKeySeed() throws Exception {
+ byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
+ byte[] sharedDhKey = TestUtils.hexStringToByteArray(IKE_SHARED_DH_KEY_HEX_STRING);
+
+ byte[] calculatedSKeySeed =
+ mIkeHmacSha1Prf.generateSKeySeed(nonceInit, nonceResp, sharedDhKey);
+
+ byte[] expectedSKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ assertArrayEquals(expectedSKeySeed, calculatedSKeySeed);
+ }
+
+ @Test
+ public void testGenerateRekeyedSKeySeed() throws Exception {
+ byte[] nonceInit = TestUtils.hexStringToByteArray(IKE_NONCE_INIT_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(IKE_NONCE_RESP_HEX_STRING);
+ byte[] sharedDhKey = TestUtils.hexStringToByteArray(IKE_SHARED_DH_KEY_HEX_STRING);
+ byte[] old_skd = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING);
+
+ byte[] calculatedSKeySeed =
+ mIkeHmacSha1Prf.generateRekeyedSKeySeed(old_skd, nonceInit, nonceResp, sharedDhKey);
+
+ // Verify that the new sKeySeed is different.
+ // TODO: Find actual test vectors to test positive case.
+ byte[] oldSKeySeed = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ assertFalse(Arrays.equals(oldSKeySeed, calculatedSKeySeed));
+ }
+
+ @Test
+ public void testGenerateKeyMatForIke() throws Exception {
+ byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SKEYSEED_HEX_STRING);
+ byte[] prfData =
+ TestUtils.hexStringToByteArray(
+ IKE_NONCE_INIT_HEX_STRING
+ + IKE_NONCE_RESP_HEX_STRING
+ + IKE_INIT_SPI
+ + IKE_RESP_SPI);
+ int keyMaterialLen =
+ IKE_SK_D_KEY_LEN
+ + IKE_AUTH_ALGO_KEY_LEN * 2
+ + IKE_ENCR_ALGO_KEY_LEN * 2
+ + IKE_PRF_KEY_LEN * 2;
+
+ byte[] calculatedKeyMat = mIkeHmacSha1Prf.generateKeyMat(prfKey, prfData, keyMaterialLen);
+
+ byte[] expectedKeyMat = TestUtils.hexStringToByteArray(IKE_KEY_MAT);
+ assertArrayEquals(expectedKeyMat, calculatedKeyMat);
+ }
+
+ @Test
+ public void testGenerateKeyMatForFirstChild() throws Exception {
+ byte[] prfKey = TestUtils.hexStringToByteArray(IKE_SK_D_HEX_STRING);
+ byte[] prfData =
+ TestUtils.hexStringToByteArray(
+ IKE_NONCE_INIT_HEX_STRING + IKE_NONCE_RESP_HEX_STRING);
+ int keyMaterialLen = FIRST_CHILD_AUTH_ALGO_KEY_LEN * 2 + FIRST_CHILD_ENCR_ALGO_KEY_LEN * 2;
+
+ byte[] calculatedKeyMat = mIkeHmacSha1Prf.generateKeyMat(prfKey, prfData, keyMaterialLen);
+
+ byte[] expectedKeyMat = TestUtils.hexStringToByteArray(FIRST_CHILD_KEY_MAT);
+ assertArrayEquals(expectedKeyMat, calculatedKeyMat);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java
new file mode 100644
index 00000000..3f3a0e10
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/crypto/IkeNormalModeCipherTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 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.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+import javax.crypto.IllegalBlockSizeException;
+
+@RunWith(JUnit4.class)
+public final class IkeNormalModeCipherTest {
+ private static final String IKE_AUTH_INIT_REQUEST_IV = "b9132b7bb9f658dfdc648e5017a6322a";
+ private static final String IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA =
+ "030c316ce55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ + "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ + "59eb4e81";
+ private static final String IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA =
+ "2400000c010000000a50500d2700000c010000000a505050"
+ + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327"
+ + "44dfb2c12c00002c00000028010304032ad4c0a20300000c"
+ + "0100000c800e008003000008030000020000000805000000"
+ + "2d00001801000000070000100000ffff00000000ffffffff"
+ + "2900001801000000070000100000ffff00000000ffffffff"
+ + "29000008000040000000000c000040010000000100000000"
+ + "000000000000000b";
+
+ private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1";
+
+ private static final int AES_BLOCK_SIZE = 16;
+
+ private IkeNormalModeCipher mAesCbcCipher;
+ private byte[] mAesCbcKey;
+
+ private byte[] mIv;
+ private byte[] mEncryptedPaddedData;
+ private byte[] mUnencryptedPaddedData;
+
+ @Before
+ public void setUp() throws Exception {
+ mAesCbcCipher =
+ (IkeNormalModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
+ SaProposal.KEY_LEN_AES_128),
+ IkeMessage.getSecurityProvider());
+ mAesCbcKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
+
+ mIv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV);
+ mEncryptedPaddedData =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA);
+ mUnencryptedPaddedData =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA);
+ }
+
+ @Test
+ public void testBuild() throws Exception {
+ assertFalse(mAesCbcCipher.isAead());
+ assertEquals(AES_BLOCK_SIZE, mAesCbcCipher.getBlockSize());
+ assertEquals(AES_BLOCK_SIZE, mAesCbcCipher.generateIv().length);
+ }
+
+ @Test
+ public void testGenerateRandomIv() throws Exception {
+ assertFalse(Arrays.equals(mAesCbcCipher.generateIv(), mAesCbcCipher.generateIv()));
+ }
+
+ @Test
+ public void testEncryptWithNormalCipher() throws Exception {
+ byte[] calculatedData = mAesCbcCipher.encrypt(mUnencryptedPaddedData, mAesCbcKey, mIv);
+
+ assertArrayEquals(mEncryptedPaddedData, calculatedData);
+ }
+
+ @Test
+ public void testDecryptWithNormalCipher() throws Exception {
+ byte[] calculatedData = mAesCbcCipher.decrypt(mEncryptedPaddedData, mAesCbcKey, mIv);
+ assertArrayEquals(mUnencryptedPaddedData, calculatedData);
+ }
+
+ @Test
+ public void testEncryptWithWrongKey() throws Exception {
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
+
+ try {
+ mAesCbcCipher.encrypt(mEncryptedPaddedData, encryptionKey, mIv);
+ fail("Expected to fail due to encryption key with wrong length.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecryptWithNormalCipherWithBadPad() throws Exception {
+ byte[] dataToDecrypt =
+ TestUtils.hexStringToByteArray(
+ IKE_AUTH_INIT_REQUEST_UNENCRYPTED_PADDED_DATA + "00");
+ try {
+ mAesCbcCipher.decrypt(dataToDecrypt, mAesCbcKey, mIv);
+ fail("Expected to fail when try to decrypt data with bad padding");
+ } catch (IllegalBlockSizeException expected) {
+
+ }
+ }
+
+ @Test
+ public void testBuildIpSecAlgorithm() throws Exception {
+ IpSecAlgorithm ipsecAlgorithm = mAesCbcCipher.buildIpSecAlgorithmWithKey(mAesCbcKey);
+
+ IpSecAlgorithm expectedIpSecAlgorithm =
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, mAesCbcKey);
+
+ assertTrue(IpSecAlgorithm.equals(expectedIpSecAlgorithm, ipsecAlgorithm));
+ }
+
+ @Test
+ public void buildIpSecAlgorithmWithInvalidKey() throws Exception {
+ byte[] encryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP + "00");
+
+ try {
+ mAesCbcCipher.buildIpSecAlgorithmWithKey(encryptionKey);
+
+ fail("Expected to fail due to encryption key with wrong length.");
+ } catch (IllegalArgumentException expected) {
+
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java
new file mode 100644
index 00000000..96921c30
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthDigitalSignPayloadTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeAuthDigitalSignPayload.SIGNATURE_ALGO_RSA_SHA2_256;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+public final class IkeAuthDigitalSignPayloadTest {
+ // TODO: Build a RSA_SHA1 signature and add tests for it.
+
+ // RSA_SHA2_256
+ private static final String AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING =
+ "0e0000000f300d06092a864886f70d01010b05006f76af4150d653c5d4136b9f"
+ + "69d905849bf075c563e6d14ccda42361ec3e7d12c72e2dece5711ea1d952f7b8"
+ + "e12c5d982aa4efdaeac36a02b222aa96242cc424";
+ private static final String SIGNATURE =
+ "6f76af4150d653c5d4136b9f69d905849bf075c563e6d14ccda42361ec3e7d12"
+ + "c72e2dece5711ea1d952f7b8e12c5d982aa4efdaeac36a02b222aa96242cc424";
+
+ private static final String IKE_INIT_RESP_HEX_STRING =
+ "02458497587b09d488d5b76480bce53d2120222000000000000001cc2200002c"
+ + "00000028010100040300000801000003030000080300000203000008020000020"
+ + "00000080400000e28000108000e000013d60e51c40922cb121e395bacbd627cdd"
+ + "d3240baa4fcefd29f65f8dd37329d68d4fb4854f8b8f07cfb60900e276d99a396"
+ + "1112ee866b5456cf588dc1092fd3bc19668fb8fa42872f51c0ee748bdb665dcbe"
+ + "15ac454f6ed966149954dac5187638d1ab61869d97a4873c4733c48cbe3acc8a6"
+ + "5cfea3ce83fd09fba174bf0ec56d73a0585859399e61c2c38e695841f8df8a511"
+ + "aadd438f56634165ad9b88e858c1585f1bee646943b8a96f5397721079a127b87"
+ + "fd286e8f869ae021ce82adf91fa360217ac32268b39b698bf06a4e89b8d0267af"
+ + "1c5b979b6493adb10a0e14aa707309e914b8d377903e75cb13cffbfde9c26842f"
+ + "b49a07a4497c9907d39515b290000244b8aed6297c09a5a0dda06c873f5573b34"
+ + "886dd779e90c19beca3fc54ab3cae02900001c00004004d8e7cb9d1e689ae8c84"
+ + "c5078355436f3347376ff2900001c0000400545bc3f2113770de91c769094f1bd"
+ + "614534e765ea290000080000402e290000100000402f000100020003000400000"
+ + "00800004014";
+ private static final String NONCE_INIT_HEX_STRING =
+ "a5dded450b5ffd2670f37954367fce28279a085c830a03358b10b0872c0578f9";
+ private static final String ID_RESP_PAYLOAD_BODY_HEX_STRING = "01000000c0a82b8a";
+ private static final String SKP_RESP_HEX_STRING = "8FE8EC3153EDE924C23D6630D3C992A494E2F256";
+
+ private static final byte[] IKE_INIT_RESP_REQUEST =
+ TestUtils.hexStringToByteArray(IKE_INIT_RESP_HEX_STRING);
+ private static final byte[] NONCE_INIT_RESP =
+ TestUtils.hexStringToByteArray(NONCE_INIT_HEX_STRING);
+ private static final byte[] ID_RESP_PAYLOAD_BODY =
+ TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_BODY_HEX_STRING);
+ private static final byte[] PRF_RESP_KEY = TestUtils.hexStringToByteArray(SKP_RESP_HEX_STRING);
+
+ private IkeMacPrf mIkeHmacSha1Prf;
+
+ @Before
+ public void setUp() throws Exception {
+ mIkeHmacSha1Prf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+ }
+
+ @Test
+ public void testDecodeGenericDigitalSignPayload() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING);
+ IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket);
+
+ assertTrue(payload instanceof IkeAuthDigitalSignPayload);
+ IkeAuthDigitalSignPayload dsPayload = (IkeAuthDigitalSignPayload) payload;
+ assertEquals(SIGNATURE_ALGO_RSA_SHA2_256, dsPayload.signatureAlgoAndHash);
+ assertArrayEquals(dsPayload.signature, TestUtils.hexStringToByteArray(SIGNATURE));
+ }
+
+ @Test
+ public void testVerifyInboundSignature() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING);
+ IkeAuthDigitalSignPayload payload =
+ (IkeAuthDigitalSignPayload) IkeAuthPayload.getIkeAuthPayload(false, inputPacket);
+
+ X509Certificate cert = CertUtils.createCertFromPemFile("end-cert-small.pem");
+
+ payload.verifyInboundSignature(
+ cert,
+ IKE_INIT_RESP_REQUEST,
+ NONCE_INIT_RESP,
+ ID_RESP_PAYLOAD_BODY,
+ mIkeHmacSha1Prf,
+ PRF_RESP_KEY);
+ }
+
+ @Test
+ public void testVerifyInboundSignatureFail() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(AUTH_PAYLOAD_BODY_GENERIC_DIGITAL_SIGN_HEX_STRING);
+ IkeAuthDigitalSignPayload payload =
+ (IkeAuthDigitalSignPayload) IkeAuthPayload.getIkeAuthPayload(false, inputPacket);
+
+ assertArrayEquals(payload.signature, TestUtils.hexStringToByteArray(SIGNATURE));
+ X509Certificate cert = CertUtils.createCertFromPemFile("end-cert-a.pem");
+
+ try {
+ payload.verifyInboundSignature(
+ cert,
+ IKE_INIT_RESP_REQUEST,
+ NONCE_INIT_RESP,
+ ID_RESP_PAYLOAD_BODY,
+ mIkeHmacSha1Prf,
+ PRF_RESP_KEY);
+ fail("Expected to fail due to wrong certificate.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testGenerateSignature() throws Exception {
+ PrivateKey key = CertUtils.createRsaPrivateKeyFromKeyFile("end-cert-key-a.key");
+
+ IkeAuthDigitalSignPayload authPayload =
+ new IkeAuthDigitalSignPayload(
+ SIGNATURE_ALGO_RSA_SHA2_256,
+ key,
+ IKE_INIT_RESP_REQUEST,
+ NONCE_INIT_RESP,
+ ID_RESP_PAYLOAD_BODY,
+ mIkeHmacSha1Prf,
+ PRF_RESP_KEY);
+
+ assertEquals(SIGNATURE_ALGO_RSA_SHA2_256, authPayload.signatureAlgoAndHash);
+ assertArrayEquals(authPayload.signature, TestUtils.hexStringToByteArray(SIGNATURE));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java
new file mode 100644
index 00000000..989b4689
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPayloadTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class IkeAuthPayloadTest {
+ private static final String PSK_AUTH_PAYLOAD_HEX_STRING =
+ "02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1";
+ private static final String PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING =
+ "df7c038aefaaa32d3f44b228b52a332744dfb2c1";
+ private static final String PSK_ID_PAYLOAD_HEX_STRING = "010000000a50500d";
+ private static final String PSK_SKP_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485";
+ private static final String PSK_SIGNED_OCTETS_APPENDIX_HEX_STRING =
+ "D83B20CC6A0932B2A7CEF26E4020ABAAB64F0C6A";
+ private static final String PSK_IKE_INIT_REQUEST_HEX_STRING =
+ "5f54bf6d8b48e6e1000000000000000021202208"
+ + "0000000000000150220000300000002c01010004"
+ + "0300000c0100000c800e00800300000803000002"
+ + "0300000804000002000000080200000228000088"
+ + "00020000b4a2faf4bb54878ae21d638512ece55d"
+ + "9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9"
+ + "ece8ac37534036040610ebdd92f46bef84f0be7d"
+ + "b860351843858f8acf87056e272377f70c9f2d81"
+ + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ + "6bbeb08214c707137607958729000024c39b7f36"
+ + "8f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7"
+ + "d83f6beb881eab2051d8663f421d10b02b00001c"
+ + "00004005d915368ca036004cb578ae3e3fb26850"
+ + "9aeab19000000020699369228741c6d4ca094c93"
+ + "e242c9de19e7b7c60000000500000500";
+ private static final String PSK_NONCE_RESP_HEX_STRING =
+ "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String PSK_INIT_SIGNED_OCTETS =
+ "5F54BF6D8B48E6E1000000000000000021202208"
+ + "0000000000000150220000300000002C01010004"
+ + "0300000C0100000C800E00800300000803000002"
+ + "0300000804000002000000080200000228000088"
+ + "00020000B4A2FAF4BB54878AE21D638512ECE55D"
+ + "9236FC5046AB6CEF82220F421F3CE6361FAF3656"
+ + "4ECB6D28798A94AAD7B2B4B603DDEAAA5630ADB9"
+ + "ECE8AC37534036040610EBDD92F46BEF84F0BE7D"
+ + "B860351843858F8ACF87056E272377F70C9F2D81"
+ + "E29C7B0CE4F291A3A72476BB0B278FD4B7B0A4C2"
+ + "6BBEB08214C707137607958729000024C39B7F36"
+ + "8F4681B89FA9B7BE6465ABD7C5F68B6ED5D3B4C7"
+ + "2CB4240EB5C464122900001C00004004E54F73B7"
+ + "D83F6BEB881EAB2051D8663F421D10B02B00001C"
+ + "00004005D915368CA036004CB578AE3E3FB26850"
+ + "9AEAB19000000020699369228741C6D4CA094C93"
+ + "E242C9DE19E7B7C600000005000005009756112C"
+ + "A539F5C25ABACC7EE92B73091942A9C06950F988"
+ + "48F1AF1694C4DDFFD83B20CC6A0932B2A7CEF26E"
+ + "4020ABAAB64F0C6A";
+
+ private static final int AUTH_METHOD_POSITION = 0;
+
+ private IkeMacPrf mIkeHmacSha1Prf;
+
+ @Before
+ public void setUp() throws Exception {
+ mIkeHmacSha1Prf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+ }
+
+ @Test
+ public void testDecodeIkeAuthPayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING);
+ IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket);
+
+ assertEquals(IkeAuthPayload.AUTH_METHOD_PRE_SHARED_KEY, payload.authMethod);
+ assertTrue(payload instanceof IkeAuthPskPayload);
+
+ byte[] expectedSignature =
+ TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING);
+ assertArrayEquals(expectedSignature, ((IkeAuthPskPayload) payload).signature);
+ }
+
+ @Test
+ public void testDecodeIkeAuthPayloadWithUnsupportedMethod() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING);
+ inputPacket[AUTH_METHOD_POSITION] = 0;
+ try {
+ IkeAuthPayload payload = IkeAuthPayload.getIkeAuthPayload(false, inputPacket);
+ fail("Expected Exception: authentication method is not supported");
+ } catch (AuthenticationFailedException e) {
+ }
+ }
+
+ @Test
+ public void testGetSignedOctets() throws Exception {
+ byte[] skpBytes = TestUtils.hexStringToByteArray(PSK_SKP_HEX_STRING);
+ byte[] idBytes = TestUtils.hexStringToByteArray(PSK_ID_PAYLOAD_HEX_STRING);
+ byte[] ikeInitRequest = TestUtils.hexStringToByteArray(PSK_IKE_INIT_REQUEST_HEX_STRING);
+ byte[] nonceResp = TestUtils.hexStringToByteArray(PSK_NONCE_RESP_HEX_STRING);
+
+ byte[] calculatedBytes =
+ IkeAuthPayload.getSignedOctets(
+ ikeInitRequest, nonceResp, idBytes, mIkeHmacSha1Prf, skpBytes);
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(PSK_INIT_SIGNED_OCTETS);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java
new file mode 100644
index 00000000..cad60522
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeAuthPskPayloadTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacPrf;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public final class IkeAuthPskPayloadTest {
+ private static final String PSK_AUTH_PAYLOAD_HEX_STRING =
+ "2100001c02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1";
+ private static final String PSK_AUTH_PAYLOAD_BODY_HEX_STRING =
+ "02000000df7c038aefaaa32d3f44b228b52a332744dfb2c1";
+ private static final String PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING =
+ "df7c038aefaaa32d3f44b228b52a332744dfb2c1";
+
+ private static final String PSK_IKE_INIT_REQUEST_HEX_STRING =
+ "5f54bf6d8b48e6e1000000000000000021202208"
+ + "0000000000000150220000300000002c01010004"
+ + "0300000c0100000c800e00800300000803000002"
+ + "0300000804000002000000080200000228000088"
+ + "00020000b4a2faf4bb54878ae21d638512ece55d"
+ + "9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9"
+ + "ece8ac37534036040610ebdd92f46bef84f0be7d"
+ + "b860351843858f8acf87056e272377f70c9f2d81"
+ + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ + "6bbeb08214c707137607958729000024c39b7f36"
+ + "8f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7"
+ + "d83f6beb881eab2051d8663f421d10b02b00001c"
+ + "00004005d915368ca036004cb578ae3e3fb26850"
+ + "9aeab19000000020699369228741c6d4ca094c93"
+ + "e242c9de19e7b7c60000000500000500";
+ private static final String PSK_NONCE_RESP_HEX_STRING =
+ "9756112ca539f5c25abacc7ee92b73091942a9c06950f98848f1af1694c4ddff";
+ private static final String PSK_ID_INITIATOR_PAYLOAD_HEX_STRING = "010000000a50500d";
+
+ private static final String PSK_HEX_STRING = "6A756E69706572313233";
+ private static final String PSK_SKP_HEX_STRING = "094787780EE466E2CB049FA327B43908BC57E485";
+
+ private static final byte[] PSK = TestUtils.hexStringToByteArray(PSK_HEX_STRING);
+ private static final byte[] IKE_INIT_REQUEST =
+ TestUtils.hexStringToByteArray(PSK_IKE_INIT_REQUEST_HEX_STRING);
+ private static final byte[] NONCE = TestUtils.hexStringToByteArray(PSK_NONCE_RESP_HEX_STRING);
+ private static final byte[] ID_PAYLOAD_BODY =
+ TestUtils.hexStringToByteArray(PSK_ID_INITIATOR_PAYLOAD_HEX_STRING);
+ private static final byte[] PRF_KEY = TestUtils.hexStringToByteArray(PSK_SKP_HEX_STRING);
+ private static final byte[] SIGNATURE =
+ TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_SIGNATURE_HEX_STRING);
+
+ private IkeMacPrf mIkeHmacSha1Prf;
+
+ @Before
+ public void setUp() throws Exception {
+ mIkeHmacSha1Prf =
+ IkeMacPrf.create(
+ new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1),
+ IkeMessage.getSecurityProvider());
+ }
+
+ @Test
+ public void testBuildOutboundIkeAuthPskPayload() throws Exception {
+ IkeAuthPskPayload payload =
+ new IkeAuthPskPayload(
+ PSK, IKE_INIT_REQUEST, NONCE, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY);
+
+ assertEquals(IkeAuthPayload.AUTH_METHOD_PRE_SHARED_KEY, payload.authMethod);
+ assertArrayEquals(SIGNATURE, payload.signature);
+
+ // Verify payload length
+ int payloadLength = payload.getPayloadLength();
+ byte[] expectedPayload = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_HEX_STRING);
+ assertEquals(expectedPayload.length, payloadLength);
+
+ // Verify encoding
+ ByteBuffer byteBuffer = ByteBuffer.allocate(payloadLength);
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_SA, byteBuffer);
+ assertArrayEquals(expectedPayload, byteBuffer.array());
+ }
+
+ private IkeAuthPskPayload buildPskPayload() throws Exception {
+ byte[] payloadBody = TestUtils.hexStringToByteArray(PSK_AUTH_PAYLOAD_BODY_HEX_STRING);
+ IkeAuthPskPayload pskPayload =
+ (IkeAuthPskPayload) IkeAuthPayload.getIkeAuthPayload(false, payloadBody);
+ return pskPayload;
+ }
+
+ @Test
+ public void testDecodeIkeAuthPskPayload() throws Exception {
+ IkeAuthPskPayload pskPayload = buildPskPayload();
+
+ assertArrayEquals(SIGNATURE, pskPayload.signature);
+ }
+
+ @Test
+ public void testVerifyReceivedSignature() throws Exception {
+ IkeAuthPskPayload pskPayload = buildPskPayload();
+
+ pskPayload.verifyInboundSignature(
+ PSK, IKE_INIT_REQUEST, NONCE, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY);
+ }
+
+ @Test
+ public void testVerifyReceivedSignatureFailure() throws Exception {
+ IkeAuthPskPayload pskPayload = buildPskPayload();
+ byte[] nonce = Arrays.copyOf(NONCE, NONCE.length);
+ nonce[0]++;
+
+ try {
+ pskPayload.verifyInboundSignature(
+ PSK, IKE_INIT_REQUEST, nonce, ID_PAYLOAD_BODY, mIkeHmacSha1Prf, PRF_KEY);
+ fail("Expected signature verification to have failed due to mismatched signatures.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java
new file mode 100644
index 00000000..2bb72e33
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertPayloadTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.fail;
+
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.testutils.CertUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+public final class IkeCertPayloadTest {
+ private X509Certificate mEndCertA;
+ private X509Certificate mEndCertB;
+ private X509Certificate mEndCertSmall;
+
+ private X509Certificate mIntermediateCertBOne;
+ private X509Certificate mIntermediateCertBTwo;
+
+ private TrustAnchor mTrustAnchorA;
+ private TrustAnchor mTrustAnchorB;
+ private TrustAnchor mTrustAnchorSmall;
+
+ @Before
+ public void setUp() throws Exception {
+ mEndCertA = CertUtils.createCertFromPemFile("end-cert-a.pem");
+ mTrustAnchorA =
+ new TrustAnchor(
+ CertUtils.createCertFromPemFile("self-signed-ca-a.pem"),
+ null /*nameConstraints*/);
+
+ mEndCertB = CertUtils.createCertFromPemFile("end-cert-b.pem");
+ mIntermediateCertBOne = CertUtils.createCertFromPemFile("intermediate-ca-b-one.pem");
+ mIntermediateCertBTwo = CertUtils.createCertFromPemFile("intermediate-ca-b-two.pem");
+ mTrustAnchorB =
+ new TrustAnchor(
+ CertUtils.createCertFromPemFile("self-signed-ca-b.pem"),
+ null /*nameConstraints*/);
+
+ mEndCertSmall = CertUtils.createCertFromPemFile("end-cert-small.pem");
+ mTrustAnchorSmall =
+ new TrustAnchor(
+ CertUtils.createCertFromPemFile("self-signed-ca-small.pem"),
+ null /*nameConstraints*/);
+ }
+
+ @Test
+ public void testValidateCertsNoIntermediateCerts() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+ certList.add(mEndCertA);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorA);
+
+ IkeCertPayload.validateCertificates(mEndCertA, certList, null /*crlList*/, trustAnchors);
+ }
+
+ @Test
+ public void testValidateCertsWithIntermediateCerts() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+
+ certList.add(mEndCertB);
+ certList.add(mIntermediateCertBTwo);
+ certList.add(mIntermediateCertBOne);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorB);
+
+ IkeCertPayload.validateCertificates(mEndCertB, certList, null /*crlList*/, trustAnchors);
+ }
+
+ @Test
+ public void testValidateCertsWithMultiTrustAnchors() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+ certList.add(mEndCertA);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorA);
+ trustAnchors.add(mTrustAnchorB);
+
+ IkeCertPayload.validateCertificates(mEndCertA, certList, null /*crlList*/, trustAnchors);
+ }
+
+ @Test
+ public void testValidateCertsWithWrongTrustAnchor() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+ certList.add(mEndCertA);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorB);
+
+ try {
+ IkeCertPayload.validateCertificates(
+ mEndCertA, certList, null /*crlList*/, trustAnchors);
+ fail("Expected to fail due to absence of valid trust anchor.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testValidateCertsWithMissingIntermediateCerts() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+ certList.add(mEndCertB);
+ certList.add(mIntermediateCertBOne);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorB);
+
+ try {
+ IkeCertPayload.validateCertificates(
+ mEndCertA, certList, null /*crlList*/, trustAnchors);
+ fail("Expected to fail due to absence of intermediate certificate.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testValidateCertsWithSmallSizeKey() throws Exception {
+ List<X509Certificate> certList = new LinkedList<>();
+ certList.add(mEndCertSmall);
+
+ Set<TrustAnchor> trustAnchors = new HashSet<>();
+ trustAnchors.add(mTrustAnchorSmall);
+
+ try {
+ IkeCertPayload.validateCertificates(
+ mEndCertSmall, certList, null /*crlList*/, trustAnchors);
+ fail("Expected to fail because certificates use small size key");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java
new file mode 100644
index 00000000..ef6ec289
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeCertX509CertPayloadTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Base64;
+
+public final class IkeCertX509CertPayloadTest {
+ private static final String CERT_PAYLOAD_BODY_HEX_STRING =
+ "043082042130820209a003020102020827a2b30cdd5043ab300d06092a864886"
+ + "f70d01010c05003035310b3009060355040613024e5a310e300c060355040a13"
+ + "054576697461311630140603550403130d457669746120526f6f74204341301e"
+ + "170d3138313033303139303034305a170d3230313032393139303034305a3036"
+ + "310b3009060355040613024e5a310e300c060355040a13054576697461311730"
+ + "150603550403130e3139322e3136382e34332e31303330820122300d06092a86"
+ + "4886f70d01010105000382010f003082010a0282010100b15dd29d25ef546532"
+ + "2b85b2db5aefbff85b2f8e90a67c5ce48a7baee696d645602b1122501e10aec4"
+ + "7733a875a6f02432a4d684e67bdab36549996f3a2fc919cf769dc3a3b270791a"
+ + "3682c868225e5478df1d2467b1afafd608db2696c484a0134f8069f9ed0b55fc"
+ + "656b15fef6b9b151debfd1d96f6bba0f032432ec6497573d47f3539b4754f33a"
+ + "89ea4d3986ad33137a750b36073fe50188c169a851c092506ea383ceb58b55ad"
+ + "f55956e037118f2f747be6dc3f0f741ec1d658defcd9197b8750a75ae9c1b1c7"
+ + "2f2ff91f561d1b3d77d54037e8cfe3738668a3de42cf7eab21353ae322e40eb4"
+ + "8dcf38eb34572118315047133f7727cab46f10f9f70de30203010001a3343032"
+ + "301f0603551d23041830168014fbcb6762ffde82b4d63d069beeac583228ab32"
+ + "25300f0603551d11040830068704c0a82b67300d06092a864886f70d01010c05"
+ + "0003820201006f26b5973346030b7297d50e3ce4cd8ca5b1527fd9045b4bb540"
+ + "a7cd0193e16732346d6fb99c2058a17f0129460ba6e4832b6b4a6c51731ceea4"
+ + "5464e0f179b860b3f0bf10956b1f4bae8cecdfc08261a3c00e8aa0ddbbe9ca08"
+ + "6a954ec9e3bcb0ea907e617e98a97ec1c2b7c2190b10a8117b12e56640830c1f"
+ + "c6492a8f7062e92b7e2d03f22173fb333b9a45d96c9842fc16c0c6658428fbdf"
+ + "6351be6fe65f7e1f329d7a07dad617837208e72539774ef46a72bc8e279fca74"
+ + "d68fbfba91de8d62331c2df135ce234b007afc330f96d3ee80888ba102334c32"
+ + "2d25a78d26a2c9434fafdfa9ea35acadf4938a69623de5ee966d1d550d851df3"
+ + "d99b477f804a6eb2d8c16f80161a9511674bfa514f5d127c856ca6c0dff07eba"
+ + "87672e2154c4fd0f3b906ff888978315a041b3fdbc7cf8e2306e0b20b3a79d2b"
+ + "5a59f7f6b8e784af57d43be4be37f9381a6ff6c3fa477fed151d586c42634e0a"
+ + "88e699a9f3b38459589ea014d549b7ed7fd551bd544d464d955476ed1c051fa1"
+ + "a7351d5d4f13efe232bc847a245c85a4a04abf66abd7d983b254a67d0189206c"
+ + "8fc8989f38e63bd827552e209a2aa119d0622f0defe08cef0bf48a3459c09ad9"
+ + "8f729b51bb57f2518385abd790ff3d80d1cdce1218f61ea45c0c6fc9814c300d"
+ + "abc24a747560744e9861c9395dd2f849b4d1196fe302ac8a063afeea9bc9d637"
+ + "2fedb79130bb";
+
+ private static final String CLIENT_END_CERTIFICATE =
+ "MIIEITCCAgmgAwIBAgIIJ6KzDN1QQ6swDQYJKoZIhvcNAQEMBQAwNTELMAkGA1UE"
+ + "BhMCTloxDjAMBgNVBAoTBUV2aXRhMRYwFAYDVQQDEw1Fdml0YSBSb290IENBMB4X"
+ + "DTE4MTAzMDE5MDA0MFoXDTIwMTAyOTE5MDA0MFowNjELMAkGA1UEBhMCTloxDjAM"
+ + "BgNVBAoTBUV2aXRhMRcwFQYDVQQDEw4xOTIuMTY4LjQzLjEwMzCCASIwDQYJKoZI"
+ + "hvcNAQEBBQADggEPADCCAQoCggEBALFd0p0l71RlMiuFstta77/4Wy+OkKZ8XOSK"
+ + "e67mltZFYCsRIlAeEK7EdzOodabwJDKk1oTme9qzZUmZbzovyRnPdp3Do7JweRo2"
+ + "gshoIl5UeN8dJGexr6/WCNsmlsSEoBNPgGn57QtV/GVrFf72ubFR3r/R2W9rug8D"
+ + "JDLsZJdXPUfzU5tHVPM6iepNOYatMxN6dQs2Bz/lAYjBaahRwJJQbqODzrWLVa31"
+ + "WVbgNxGPL3R75tw/D3QewdZY3vzZGXuHUKda6cGxxy8v+R9WHRs9d9VAN+jP43OG"
+ + "aKPeQs9+qyE1OuMi5A60jc846zRXIRgxUEcTP3cnyrRvEPn3DeMCAwEAAaM0MDIw"
+ + "HwYDVR0jBBgwFoAU+8tnYv/egrTWPQab7qxYMiirMiUwDwYDVR0RBAgwBocEwKgr"
+ + "ZzANBgkqhkiG9w0BAQwFAAOCAgEAbya1lzNGAwtyl9UOPOTNjKWxUn/ZBFtLtUCn"
+ + "zQGT4WcyNG1vuZwgWKF/ASlGC6bkgytrSmxRcxzupFRk4PF5uGCz8L8QlWsfS66M"
+ + "7N/AgmGjwA6KoN276coIapVOyeO8sOqQfmF+mKl+wcK3whkLEKgRexLlZkCDDB/G"
+ + "SSqPcGLpK34tA/Ihc/szO5pF2WyYQvwWwMZlhCj732NRvm/mX34fMp16B9rWF4Ny"
+ + "COclOXdO9GpyvI4nn8p01o+/upHejWIzHC3xNc4jSwB6/DMPltPugIiLoQIzTDIt"
+ + "JaeNJqLJQ0+v36nqNayt9JOKaWI95e6WbR1VDYUd89mbR3+ASm6y2MFvgBYalRFn"
+ + "S/pRT10SfIVspsDf8H66h2cuIVTE/Q87kG/4iJeDFaBBs/28fPjiMG4LILOnnSta"
+ + "Wff2uOeEr1fUO+S+N/k4Gm/2w/pHf+0VHVhsQmNOCojmmanzs4RZWJ6gFNVJt+1/"
+ + "1VG9VE1GTZVUdu0cBR+hpzUdXU8T7+IyvIR6JFyFpKBKv2ar19mDslSmfQGJIGyP"
+ + "yJifOOY72CdVLiCaKqEZ0GIvDe/gjO8L9Io0WcCa2Y9ym1G7V/JRg4Wr15D/PYDR"
+ + "zc4SGPYepFwMb8mBTDANq8JKdHVgdE6YYck5XdL4SbTRGW/jAqyKBjr+6pvJ1jcv"
+ + "7beRMLs=";
+ private static final int CERTIFICATE_OFFSET = 1;
+
+ @Test
+ public void testDecodeX509Certificate() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING);
+ IkeCertPayload certPayload = IkeCertPayload.getIkeCertPayload(false, inputPacket);
+
+ assertTrue(certPayload instanceof IkeCertX509CertPayload);
+ X509Certificate expectedCert = pemStringToCertificate(CLIENT_END_CERTIFICATE);
+ assertEquals(expectedCert, ((IkeCertX509CertPayload) certPayload).certificate);
+ }
+
+ @Test
+ public void testDecodeX509CertificateWithUnexpectedTrailing() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING + "ffff");
+ try {
+ IkeCertPayload.getIkeCertPayload(false, inputPacket);
+ fail("Expected AuthenticationFailedException: " + "Unexpected trailing bytes.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeGetNoX509Certificate() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING);
+ inputPacket[CERTIFICATE_OFFSET] = 0;
+ try {
+ IkeCertPayload.getIkeCertPayload(false, inputPacket);
+ fail("Expected AuthenticationFailedException: " + "No certificate got.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeInvalidX509Certificate() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(CERT_PAYLOAD_BODY_HEX_STRING);
+ try {
+ IkeCertPayload.getIkeCertPayload(
+ false, Arrays.copyOfRange(inputPacket, 0, inputPacket.length - 1));
+ fail("Expected AuthenticationFailedException: " + "Certificate parsing exception.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ private X509Certificate pemStringToCertificate(String certPemStr) throws Exception {
+ CertificateFactory factory =
+ CertificateFactory.getInstance("X.509", IkeMessage.getSecurityProvider());
+ Base64.Decoder bs64Decoder = Base64.getDecoder();
+ byte[] decodedBytes = bs64Decoder.decode(certPemStr);
+ return (X509Certificate)
+ factory.generateCertificate(new ByteArrayInputStream(decodedBytes));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java
new file mode 100644
index 00000000..9fc32229
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeConfigPayloadTest.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_ADDRESS;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DHCP;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_DNS;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_NETMASK;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_SUBNET;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_ADDRESS;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_DNS;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_SUBNET;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_TYPE_REPLY;
+import static com.android.internal.net.ipsec.ike.message.IkeConfigPayload.CONFIG_TYPE_REQUEST;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_CP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.LinkAddress;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttrIpv4AddressBase;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttrIpv6AddrRangeBase;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttribute;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Address;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Dhcp;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Dns;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Netmask;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv4Subnet;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Address;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Dns;
+import com.android.internal.net.ipsec.ike.message.IkeConfigPayload.ConfigAttributeIpv6Subnet;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+public final class IkeConfigPayloadTest {
+ private static final String CONFIG_REQ_PAYLOAD_HEX =
+ "2900001801000000000100000008000000030000000a0000";
+ private static final String CONFIG_RESP_PAYLOAD_HEX =
+ "210000200200000000010004c000026400030004080808080003000408080404";
+ private static final String CONFIG_RESP_PAYLOAD_INVALID_ONE_HEX =
+ "210000200200000000010004c000026400020004fffffffe00020004fffffffe";
+ private static final String CONFIG_RESP_PAYLOAD_INVALID_TWO_HEX =
+ "210000100200000000020004fffffffe";
+
+ private static final byte[] CONFIG_REQ_PAYLOAD =
+ TestUtils.hexStringToByteArray(CONFIG_REQ_PAYLOAD_HEX);
+ private static final byte[] CONFIG_RESP_PAYLOAD =
+ TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_HEX);
+
+ private static final Inet4Address IPV4_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+ private static final Inet4Address IPV4_NETMASK =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.240"));
+ private static final int IP4_PREFIX_LEN = 28;
+ private static final LinkAddress IPV4_LINK_ADDRESS =
+ new LinkAddress(IPV4_ADDRESS, IP4_PREFIX_LEN);
+
+ private static final byte[] IPV4_ADDRESS_ATTRIBUTE_WITH_VALUE =
+ TestUtils.hexStringToByteArray("00010004c0000264");
+ private static final byte[] IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("00010000");
+
+ private static final byte[] IPV4_NETMASK_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("00020000");
+
+ private static final Inet4Address IPV4_DNS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.8.8"));
+ private static final byte[] IPV4_DNS_ATTRIBUTE_VALUE =
+ TestUtils.hexStringToByteArray("08080808");
+ private static final byte[] IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("00030000");
+
+ private static final Inet4Address IPV4_DHCP =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200"));
+ private static final byte[] IPV4_DHCP_ATTRIBUTE_WITH_VALUE =
+ TestUtils.hexStringToByteArray("00060004c00002c8");
+ private static final byte[] IPV4_DHCP_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("00060000");
+
+ private static final byte[] IPV4_SUBNET_ATTRIBUTE_VALUE =
+ TestUtils.hexStringToByteArray("c0000264fffffff0");
+ private static final byte[] IPV4_SUBNET_ATTRIBUTE_WITH_VALUE =
+ TestUtils.hexStringToByteArray("000d0008c0000264fffffff0");
+ private static final byte[] IPV4_SUBNET_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("000d0000");
+
+ private static final Inet6Address IPV6_ADDRESS =
+ (Inet6Address) (InetAddressUtils.parseNumericAddress("2001:db8::1"));
+ private static final int IP6_PREFIX_LEN = 64;
+ private static final LinkAddress IPV6_LINK_ADDRESS =
+ new LinkAddress(IPV6_ADDRESS, IP6_PREFIX_LEN);
+
+ private static final byte[] IPV6_ADDRESS_ATTRIBUTE_VALUE =
+ TestUtils.hexStringToByteArray("20010db800000000000000000000000140");
+ private static final byte[] IPV6_ADDRESS_ATTRIBUTE_WITH_VALUE =
+ TestUtils.hexStringToByteArray("0008001120010db800000000000000000000000140");
+ private static final byte[] IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("00080000");
+
+ private static final byte[] IPV6_SUBNET_ATTRIBUTE_VALUE = IPV6_ADDRESS_ATTRIBUTE_VALUE;
+ private static final byte[] IPV6_SUBNET_ATTRIBUTE_WITH_VALUE =
+ TestUtils.hexStringToByteArray("000f001120010db800000000000000000000000140");
+ private static final byte[] IPV6_SUBNET_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("000f0000");
+
+ private static final Inet6Address IPV6_DNS =
+ (Inet6Address) (InetAddressUtils.parseNumericAddress("2001:db8:100::1"));
+ private static final byte[] IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE =
+ TestUtils.hexStringToByteArray("000a0000");
+
+ private Inet4Address[] mNetMasks;
+ private int[] mIpv4PrefixLens;
+
+ @Before
+ public void setUp() throws Exception {
+ mNetMasks =
+ new Inet4Address[] {
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("0.0.0.0")),
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.255")),
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("255.255.255.240"))
+ };
+ mIpv4PrefixLens = new int[] {0, 32, 28};
+ }
+
+ private IkeConfigPayload verifyDecodeHeaderAndGetPayload(
+ IkePayload payload, int expectedConfigType) {
+ assertEquals(PAYLOAD_TYPE_CP, payload.payloadType);
+ assertFalse(payload.isCritical);
+ assertTrue(payload instanceof IkeConfigPayload);
+
+ IkeConfigPayload configPayload = (IkeConfigPayload) payload;
+ assertEquals(expectedConfigType, configPayload.configType);
+
+ return configPayload;
+ }
+
+ @Test
+ public void testDecodeConfigRequest() throws Exception {
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ PAYLOAD_TYPE_CP,
+ false /*isResp*/,
+ ByteBuffer.wrap(CONFIG_REQ_PAYLOAD))
+ .first;
+
+ IkeConfigPayload configPayload =
+ verifyDecodeHeaderAndGetPayload(payload, CONFIG_TYPE_REQUEST);
+
+ List<ConfigAttribute> recognizedAttributeList = configPayload.recognizedAttributeList;
+ assertEquals(4, recognizedAttributeList.size());
+
+ ConfigAttribute att = recognizedAttributeList.get(0);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, att.attributeType);
+ assertNull(((ConfigAttributeIpv4Address) att).address);
+
+ att = recognizedAttributeList.get(1);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, att.attributeType);
+ assertNull(((ConfigAttributeIpv6Address) att).linkAddress);
+
+ att = recognizedAttributeList.get(2);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType);
+ assertNull(((ConfigAttributeIpv4Dns) att).address);
+
+ att = recognizedAttributeList.get(3);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, att.attributeType);
+ assertNull(((ConfigAttributeIpv6Dns) att).address);
+ }
+
+ @Test
+ public void testDecodeConfigResponse() throws Exception {
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ PAYLOAD_TYPE_CP,
+ true /*isResp*/,
+ ByteBuffer.wrap(CONFIG_RESP_PAYLOAD))
+ .first;
+
+ IkeConfigPayload configPayload =
+ verifyDecodeHeaderAndGetPayload(payload, CONFIG_TYPE_REPLY);
+
+ List<ConfigAttribute> recognizedAttributeList = configPayload.recognizedAttributeList;
+ assertEquals(3, recognizedAttributeList.size());
+
+ ConfigAttribute att = recognizedAttributeList.get(0);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, att.attributeType);
+ assertEquals(IPV4_ADDRESS, ((ConfigAttributeIpv4Address) att).address);
+
+ att = recognizedAttributeList.get(1);
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType);
+ assertEquals(IPV4_DNS, ((ConfigAttributeIpv4Dns) att).address);
+
+ att = recognizedAttributeList.get(2);
+ InetAddress expectedDns = InetAddress.getByName("8.8.4.4");
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, att.attributeType);
+ assertEquals(expectedDns, ((ConfigAttributeIpv4Dns) att).address);
+ }
+
+ @Test
+ public void testDecodeConfigRespWithTwoNetmask() throws Exception {
+ byte[] configPayloadBytes =
+ TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_INVALID_ONE_HEX);
+ try {
+ IkePayloadFactory.getIkePayload(
+ PAYLOAD_TYPE_CP, true /*isResp*/, ByteBuffer.wrap(configPayloadBytes));
+ fail("Expected to fail because more than on netmask found");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeConfigRespNetmaskFoundWithoutIpv4Addr() throws Exception {
+ byte[] configPayloadBytes =
+ TestUtils.hexStringToByteArray(CONFIG_RESP_PAYLOAD_INVALID_TWO_HEX);
+ try {
+ IkePayloadFactory.getIkePayload(
+ PAYLOAD_TYPE_CP, true /*isResp*/, ByteBuffer.wrap(configPayloadBytes));
+ fail("Expected to fail because netmask is found without a IPv4 address");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ private ConfigAttribute makeMockAttribute(byte[] encodedAttribute) {
+ ConfigAttribute mockAttribute = mock(ConfigAttribute.class);
+
+ when(mockAttribute.getAttributeLen()).thenReturn(encodedAttribute.length);
+
+ doAnswer(
+ (invocation) -> {
+ ByteBuffer buffer = (ByteBuffer) invocation.getArguments()[0];
+ buffer.put(encodedAttribute);
+ return null;
+ })
+ .when(mockAttribute)
+ .encodeAttributeToByteBuffer(any(ByteBuffer.class));
+
+ return mockAttribute;
+ }
+
+ @Test
+ public void testBuildAndEncodeOutboundConfig() throws Exception {
+ List<ConfigAttribute> mockAttributeList = new LinkedList<>();
+ mockAttributeList.add(makeMockAttribute(IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE));
+ mockAttributeList.add(makeMockAttribute(IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE));
+ mockAttributeList.add(makeMockAttribute(IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE));
+ mockAttributeList.add(makeMockAttribute(IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE));
+ IkeConfigPayload configPayload = new IkeConfigPayload(false /*isReply*/, mockAttributeList);
+
+ assertEquals(PAYLOAD_TYPE_CP, configPayload.payloadType);
+ assertFalse(configPayload.isCritical);
+ assertEquals(CONFIG_TYPE_REQUEST, configPayload.configType);
+ assertEquals(mockAttributeList, configPayload.recognizedAttributeList);
+
+ ByteBuffer buffer = ByteBuffer.allocate(configPayload.getPayloadLength());
+ configPayload.encodeToByteBuffer(PAYLOAD_TYPE_NOTIFY, buffer);
+ assertArrayEquals(CONFIG_REQ_PAYLOAD, buffer.array());
+ }
+
+ private void verifyBuildAndEncodeAttributeCommon(
+ ConfigAttribute attribute, int expectedAttributeType, byte[] expectedEncodedAttribute) {
+ assertEquals(expectedAttributeType, attribute.attributeType);
+
+ ByteBuffer buffer = ByteBuffer.allocate(attribute.getAttributeLen());
+ attribute.encodeAttributeToByteBuffer(buffer);
+ assertArrayEquals(expectedEncodedAttribute, buffer.array());
+ }
+
+ private void verifyEncodeIpv4AddresBaseAttribute(
+ ConfigAttrIpv4AddressBase attribute,
+ int expectedAttributeType,
+ byte[] expectedEncodedAttribute,
+ Inet4Address expectedAddress) {
+ verifyBuildAndEncodeAttributeCommon(
+ attribute, expectedAttributeType, expectedEncodedAttribute);
+ assertEquals(expectedAddress, attribute.address);
+ }
+
+ private void verifyEncodeIpv6RangeBaseAttribute(
+ ConfigAttrIpv6AddrRangeBase attribute,
+ int expectedAttributeType,
+ byte[] expectedEncodedAttribute,
+ LinkAddress expectedLinkAddress) {
+ verifyBuildAndEncodeAttributeCommon(
+ attribute, expectedAttributeType, expectedEncodedAttribute);
+ assertEquals(expectedLinkAddress, attribute.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv4AddressWithValue() throws Exception {
+ ConfigAttributeIpv4Address attributeIp4Address =
+ new ConfigAttributeIpv4Address(IPV4_ADDRESS.getAddress());
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, attributeIp4Address.attributeType);
+ assertEquals(IPV4_ADDRESS, attributeIp4Address.address);
+ }
+
+ @Test
+ public void testDecodeIpv4AddressWithoutValue() throws Exception {
+ ConfigAttributeIpv4Address attributeIp4Address =
+ new ConfigAttributeIpv4Address(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_ADDRESS, attributeIp4Address.attributeType);
+ assertNull(attributeIp4Address.address);
+ }
+
+ @Test
+ public void testDecodeIpv4AddressWithInvalidValue() throws Exception {
+ byte[] invalidValue = new byte[] {1};
+
+ try {
+ ConfigAttributeIpv4Address attributeIp4Address =
+ new ConfigAttributeIpv4Address(invalidValue);
+ fail("Expected to fail due to invalid attribute value");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeIpv4AddressWithValue() throws Exception {
+ ConfigAttributeIpv4Address attributeIp4Address =
+ new ConfigAttributeIpv4Address(IPV4_ADDRESS);
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attributeIp4Address,
+ CONFIG_ATTR_INTERNAL_IP4_ADDRESS,
+ IPV4_ADDRESS_ATTRIBUTE_WITH_VALUE,
+ IPV4_ADDRESS);
+ }
+
+ @Test
+ public void testEncodeIpv4AddressWithoutValue() throws Exception {
+ ConfigAttributeIpv4Address attributeIp4Address = new ConfigAttributeIpv4Address();
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attributeIp4Address,
+ CONFIG_ATTR_INTERNAL_IP4_ADDRESS,
+ IPV4_ADDRESS_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv4NetmaskWithValue() throws Exception {
+ ConfigAttributeIpv4Netmask attribute =
+ new ConfigAttributeIpv4Netmask(IPV4_NETMASK.getAddress());
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_NETMASK, attribute.attributeType);
+ assertEquals(IPV4_NETMASK, attribute.address);
+ }
+
+ @Test
+ public void testDecodeIpv4NetmaskWithoutValue() throws Exception {
+ ConfigAttributeIpv4Netmask attribute = new ConfigAttributeIpv4Netmask(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_NETMASK, attribute.attributeType);
+ assertNull(attribute.address);
+ }
+
+ @Test
+ public void testEncodeIpv4Netmask() throws Exception {
+ ConfigAttributeIpv4Netmask attribute = new ConfigAttributeIpv4Netmask();
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attribute,
+ CONFIG_ATTR_INTERNAL_IP4_NETMASK,
+ IPV4_NETMASK_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv4DnsWithValue() throws Exception {
+ ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns(IPV4_DNS.getAddress());
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, attribute.attributeType);
+ assertEquals(IPV4_DNS, attribute.address);
+ }
+
+ @Test
+ public void testDecodeIpv4DnsWithoutValue() throws Exception {
+ ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DNS, attribute.attributeType);
+ assertNull(attribute.address);
+ }
+
+ @Test
+ public void testEncodeIpv4Dns() throws Exception {
+ ConfigAttributeIpv4Dns attribute = new ConfigAttributeIpv4Dns();
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attribute,
+ CONFIG_ATTR_INTERNAL_IP4_DNS,
+ IPV4_DNS_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv4DhcpWithValue() throws Exception {
+ ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp(IPV4_DHCP.getAddress());
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DHCP, attribute.attributeType);
+ assertEquals(IPV4_DHCP, attribute.address);
+ }
+
+ @Test
+ public void testDecodeIpv4DhcpWithoutValue() throws Exception {
+ ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_DHCP, attribute.attributeType);
+ assertNull(attribute.address);
+ }
+
+ @Test
+ public void testEncodeIpv4DhcpWithValue() throws Exception {
+ ConfigAttributeIpv4Dhcp attributeIp4Dhcp = new ConfigAttributeIpv4Dhcp(IPV4_DHCP);
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attributeIp4Dhcp,
+ CONFIG_ATTR_INTERNAL_IP4_DHCP,
+ IPV4_DHCP_ATTRIBUTE_WITH_VALUE,
+ IPV4_DHCP);
+ }
+
+ @Test
+ public void testEncodeIpv4DhcpWithoutValue() throws Exception {
+ ConfigAttributeIpv4Dhcp attribute = new ConfigAttributeIpv4Dhcp();
+
+ verifyEncodeIpv4AddresBaseAttribute(
+ attribute,
+ CONFIG_ATTR_INTERNAL_IP4_DHCP,
+ IPV4_DHCP_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv4SubnetWithValue() throws Exception {
+ ConfigAttributeIpv4Subnet attributeIp4Subnet =
+ new ConfigAttributeIpv4Subnet(IPV4_SUBNET_ATTRIBUTE_VALUE);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_SUBNET, attributeIp4Subnet.attributeType);
+ assertEquals(IPV4_LINK_ADDRESS, attributeIp4Subnet.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv4SubnetWithoutValue() throws Exception {
+ ConfigAttributeIpv4Subnet attributeIp4Subnet = new ConfigAttributeIpv4Subnet(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP4_SUBNET, attributeIp4Subnet.attributeType);
+ assertNull(attributeIp4Subnet.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv4SubnetWithInvalidValue() throws Exception {
+ byte[] ipAddress = IPV4_ADDRESS.getAddress();
+ ByteBuffer buffer = ByteBuffer.allocate(ipAddress.length * 2);
+ buffer.put(ipAddress).put(ipAddress);
+
+ try {
+ new ConfigAttributeIpv4Subnet(buffer.array());
+ fail("Expected to fail due to invalid netmask.");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeIpv4SubnetWithoutValue() throws Exception {
+ ConfigAttributeIpv4Subnet attributeIp4Subnet = new ConfigAttributeIpv4Subnet();
+
+ verifyBuildAndEncodeAttributeCommon(
+ attributeIp4Subnet,
+ CONFIG_ATTR_INTERNAL_IP4_SUBNET,
+ IPV4_SUBNET_ATTRIBUTE_WITHOUT_VALUE);
+ assertNull(attributeIp4Subnet.linkAddress);
+ }
+
+ @Test
+ public void testNetmaskToPrefixLen() throws Exception {
+ for (int i = 0; i < mNetMasks.length; i++) {
+ assertEquals(mIpv4PrefixLens[i], ConfigAttribute.netmaskToPrefixLen(mNetMasks[i]));
+ }
+ }
+
+ @Test
+ public void testPrefixToNetmaskBytes() throws Exception {
+ for (int i = 0; i < mIpv4PrefixLens.length; i++) {
+ assertArrayEquals(
+ mNetMasks[i].getAddress(),
+ ConfigAttribute.prefixToNetmaskBytes(mIpv4PrefixLens[i]));
+ }
+ }
+
+ @Test
+ public void testDecodeIpv6AddressWithValue() throws Exception {
+ ConfigAttributeIpv6Address attributeIp6Address =
+ new ConfigAttributeIpv6Address(IPV6_ADDRESS_ATTRIBUTE_VALUE);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, attributeIp6Address.attributeType);
+ assertEquals(IPV6_LINK_ADDRESS, attributeIp6Address.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv6AddressWithoutValue() throws Exception {
+ ConfigAttributeIpv6Address attributeIp6Address =
+ new ConfigAttributeIpv6Address(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_ADDRESS, attributeIp6Address.attributeType);
+ assertNull(attributeIp6Address.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv6AddressWithInvalidValue() throws Exception {
+ byte[] invalidValue = new byte[] {1};
+
+ try {
+ ConfigAttributeIpv6Address attributeIp6Address =
+ new ConfigAttributeIpv6Address(invalidValue);
+ fail("Expected to fail due to invalid attribute value");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeIpv6AddressWithValue() throws Exception {
+ ConfigAttributeIpv6Address attributeIp6Address =
+ new ConfigAttributeIpv6Address(IPV6_LINK_ADDRESS);
+
+ verifyEncodeIpv6RangeBaseAttribute(
+ attributeIp6Address,
+ CONFIG_ATTR_INTERNAL_IP6_ADDRESS,
+ IPV6_ADDRESS_ATTRIBUTE_WITH_VALUE,
+ IPV6_LINK_ADDRESS);
+ }
+
+ @Test
+ public void testEncodeIpv6AddressWithoutValue() throws Exception {
+ ConfigAttributeIpv6Address attributeIp6Address = new ConfigAttributeIpv6Address();
+
+ verifyEncodeIpv6RangeBaseAttribute(
+ attributeIp6Address,
+ CONFIG_ATTR_INTERNAL_IP6_ADDRESS,
+ IPV6_ADDRESS_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedLinkAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv6SubnetWithValue() throws Exception {
+ ConfigAttributeIpv6Subnet attributeIp6Subnet =
+ new ConfigAttributeIpv6Subnet(IPV6_SUBNET_ATTRIBUTE_VALUE);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_SUBNET, attributeIp6Subnet.attributeType);
+ assertEquals(IPV6_LINK_ADDRESS, attributeIp6Subnet.linkAddress);
+ }
+
+ @Test
+ public void testDecodeIpv6SubnetWithoutValue() throws Exception {
+ ConfigAttributeIpv6Subnet attributeIp6Subnet = new ConfigAttributeIpv6Subnet(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_SUBNET, attributeIp6Subnet.attributeType);
+ assertNull(attributeIp6Subnet.linkAddress);
+ }
+
+ @Test
+ public void testEncodeIpv6SubnetWithoutValue() throws Exception {
+ ConfigAttributeIpv6Subnet attributeIp6Subnet = new ConfigAttributeIpv6Subnet();
+
+ verifyEncodeIpv6RangeBaseAttribute(
+ attributeIp6Subnet,
+ CONFIG_ATTR_INTERNAL_IP6_SUBNET,
+ IPV6_SUBNET_ATTRIBUTE_WITHOUT_VALUE,
+ null /*expectedLinkAddress*/);
+ }
+
+ @Test
+ public void testDecodeIpv6DnsWithValue() throws Exception {
+ ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns(IPV6_DNS.getAddress());
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, attribute.attributeType);
+ assertEquals(IPV6_DNS, attribute.address);
+ }
+
+ @Test
+ public void testDecodeIpv6DnsWithoutValue() throws Exception {
+ ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns(new byte[0]);
+
+ assertEquals(CONFIG_ATTR_INTERNAL_IP6_DNS, attribute.attributeType);
+ assertNull(attribute.address);
+ }
+
+ @Test
+ public void testEncodeIpv6Dns() throws Exception {
+ ConfigAttributeIpv6Dns attribute = new ConfigAttributeIpv6Dns();
+
+ verifyBuildAndEncodeAttributeCommon(
+ attribute, CONFIG_ATTR_INTERNAL_IP6_DNS, IPV6_DNS_ATTRIBUTE_WITHOUT_VALUE);
+ assertNull(attribute.address);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java
new file mode 100644
index 00000000..6be5336f
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeDeletePayloadTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_ESP;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PROTOCOL_ID_IKE;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.SPI_LEN_IPSEC;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.SPI_LEN_NOT_INCLUDED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public final class IkeDeletePayloadTest {
+ private static final String DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000";
+ private static final String DELETE_CHILD_PAYLOAD_HEX_STRING = "0000000c030400012ad4c0a2";
+ private static final String CHILD_SPI = "2ad4c0a2";
+
+ private static final String DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING =
+ "0000001003040002abcdef0120fedcba";
+ private static final String[] MULTIPLE_CHILD_SPIS = new String[] {"abcdef01", "20fedcba"};
+
+ private static final int NUM_CHILD_SPI = 1;
+
+ private static final int PROTOCOL_ID_OFFSET = 4;
+ private static final int SPI_SIZE_OFFSET = 5;
+ private static final int NUM_OF_SPI_OFFSET = 6;
+
+ @Test
+ public void testDecodeDeleteIkePayload() throws Exception {
+ ByteBuffer inputBuffer =
+ ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING));
+
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer)
+ .first;
+
+ assertTrue(payload instanceof IkeDeletePayload);
+
+ IkeDeletePayload deletePayload = (IkeDeletePayload) payload;
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, deletePayload.spiSize);
+ assertEquals(0, deletePayload.numSpi);
+ assertArrayEquals(new int[0], deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDecodeDeleteChildPayload() throws Exception {
+ ByteBuffer inputBuffer =
+ ByteBuffer.wrap(TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING));
+
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer)
+ .first;
+
+ assertTrue(payload instanceof IkeDeletePayload);
+
+ IkeDeletePayload deletePayload = (IkeDeletePayload) payload;
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, deletePayload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IPSEC, deletePayload.spiSize);
+ assertEquals(NUM_CHILD_SPI, deletePayload.numSpi);
+
+ int expectedChildSpi = TestUtils.hexStringToInt(CHILD_SPI);
+ assertArrayEquals(new int[] {expectedChildSpi}, deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testDecodeWithInvalidProtocol() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[PROTOCOL_ID_OFFSET] = -1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to unrecognized protocol ID.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidSpiSize() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[SPI_SIZE_OFFSET] = IkePayload.SPI_LEN_IPSEC;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to invalid SPI size in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidNumSpi() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[NUM_OF_SPI_OFFSET] = 1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail because number of SPI is not zero in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testDecodeWithInvalidNumSpiAndSpiSize() throws Exception {
+ byte[] deletePayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ deletePayloadBytes[SPI_SIZE_OFFSET] = 1;
+ deletePayloadBytes[NUM_CHILD_SPI] = 4;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(deletePayloadBytes);
+
+ try {
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_DELETE, false /*is request*/, inputBuffer);
+ fail("Expected to fail due to invalid SPI size in Delete IKE Payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testOutboundConstructorForIke() throws Exception {
+ IkeDeletePayload deletePayload = new IkeDeletePayload();
+
+ assertEquals(PROTOCOL_ID_IKE, deletePayload.protocolId);
+ assertEquals(SPI_LEN_NOT_INCLUDED, deletePayload.spiSize);
+ assertEquals(0, deletePayload.numSpi);
+ assertEquals(0, deletePayload.spisToDelete.length);
+ }
+
+ @Test
+ public void testOutboundConstructorWithSingleChildSa() throws Exception {
+ int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)};
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+
+ assertEquals(PROTOCOL_ID_ESP, deletePayload.protocolId);
+ assertEquals(SPI_LEN_IPSEC, deletePayload.spiSize);
+ assertEquals(NUM_CHILD_SPI, deletePayload.numSpi);
+ assertArrayEquals(childSpis, deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testOutboundConstructorWithMultipleChildSas() throws Exception {
+ int[] childSpis = new int[] {0x1, 0x2, 0xfffffffd, 0xffffffff};
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+
+ assertEquals(PROTOCOL_ID_ESP, deletePayload.protocolId);
+ assertEquals(SPI_LEN_IPSEC, deletePayload.spiSize);
+ assertEquals(childSpis.length, deletePayload.numSpi);
+ assertArrayEquals(childSpis, deletePayload.spisToDelete);
+ }
+
+ @Test
+ public void testOutboundConstructorWithNoChildSas() throws Exception {
+ try {
+ IkeDeletePayload deletePayload = new IkeDeletePayload(new int[] {});
+ fail("Expected exception for invalid SPI list");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeForIke() throws Exception {
+ IkeDeletePayload deletePayload = new IkeDeletePayload();
+ ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength());
+
+ deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb);
+
+ byte[] expectedPayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedPayloadBytes, bb.array());
+ }
+
+ @Test
+ public void testEncodeWithSingleChildSa() throws Exception {
+ int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)};
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+ ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength());
+
+ deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb);
+
+ byte[] expectedPayloadBytes =
+ TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedPayloadBytes, bb.array());
+ }
+
+ @Test
+ public void testEncodeWithMultipleChildSas() throws Exception {
+ int[] childSpis =
+ Arrays.stream(MULTIPLE_CHILD_SPIS)
+ .mapToInt(val -> TestUtils.hexStringToInt(val))
+ .toArray();
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+ ByteBuffer bb = ByteBuffer.allocate(deletePayload.getPayloadLength());
+
+ deletePayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, bb);
+
+ byte[] expectedPayloadBytes =
+ TestUtils.hexStringToByteArray(DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedPayloadBytes, bb.array());
+ }
+
+ @Test
+ public void testPayloadLengthForIke() throws Exception {
+ IkeDeletePayload deletePayload = new IkeDeletePayload();
+
+ byte[] expectedPayloadBytes = TestUtils.hexStringToByteArray(DELETE_IKE_PAYLOAD_HEX_STRING);
+ assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength());
+ }
+
+ @Test
+ public void testPayloadLengthWithSingleChildSa() throws Exception {
+ int[] childSpis = new int[] {TestUtils.hexStringToInt(CHILD_SPI)};
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+
+ byte[] expectedPayloadBytes =
+ TestUtils.hexStringToByteArray(DELETE_CHILD_PAYLOAD_HEX_STRING);
+ assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength());
+ }
+
+ @Test
+ public void testPayloadLengthWithMultipleChildSas() throws Exception {
+ int[] childSpis =
+ Arrays.stream(MULTIPLE_CHILD_SPIS)
+ .mapToInt(val -> TestUtils.hexStringToInt(val))
+ .toArray();
+ IkeDeletePayload deletePayload = new IkeDeletePayload(childSpis);
+
+ byte[] expectedPayloadBytes =
+ TestUtils.hexStringToByteArray(DELETE_MULTIPLE_CHILD_PAYLOAD_HEX_STRING);
+ assertEquals(expectedPayloadBytes.length, deletePayload.getPayloadLength());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java
new file mode 100644
index 00000000..fff82b60
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEapPayloadTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static com.android.internal.net.TestUtils.hexStringToByteArray;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class IkeEapPayloadTest {
+ private static final String EAP_SUCCESS_STRING = "03010004";
+ private static final byte[] EAP_SUCCESS_PACKET = hexStringToByteArray(EAP_SUCCESS_STRING);
+
+ private static final byte[] IKE_EAP_PAYLOAD =
+ hexStringToByteArray(
+ "00000008" + EAP_SUCCESS_STRING);
+
+ @Test
+ public void testDecodeIkeEapPayload() throws Exception {
+ ByteBuffer input = ByteBuffer.wrap(IKE_EAP_PAYLOAD);
+ IkePayload result = IkePayloadFactory
+ .getIkePayload(IkePayload.PAYLOAD_TYPE_EAP, true, input).first;
+
+ assertTrue(result instanceof IkeEapPayload);
+ IkeEapPayload ikeEapPayload = (IkeEapPayload) result;
+ assertArrayEquals(EAP_SUCCESS_PACKET, ikeEapPayload.eapMessage);
+ }
+
+ @Test
+ public void testEncodeToByteBuffer() {
+ IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET);
+ ByteBuffer result = ByteBuffer.allocate(IKE_EAP_PAYLOAD.length);
+
+ ikeEapPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, result);
+ assertArrayEquals(IKE_EAP_PAYLOAD, result.array());
+ }
+
+ @Test
+ public void testOutboundConstructorForIke() {
+ IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET);
+ assertArrayEquals(EAP_SUCCESS_PACKET, ikeEapPayload.eapMessage);
+ }
+
+ @Test
+ public void testGetPayloadLength() {
+ IkeEapPayload ikeEapPayload = new IkeEapPayload(EAP_SUCCESS_PACKET);
+ assertEquals(IKE_EAP_PAYLOAD.length, ikeEapPayload.getPayloadLength());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java
new file mode 100644
index 00000000..36c7648a
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeEncryptedPayloadBodyTest.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeCombinedModeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+public final class IkeEncryptedPayloadBodyTest {
+ private static final String IKE_AUTH_INIT_REQUEST_HEADER =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec";
+ private static final String IKE_AUTH_INIT_REQUEST_SK_HEADER = "230000d0";
+ private static final String IKE_AUTH_INIT_REQUEST_IV = "b9132b7bb9f658dfdc648e5017a6322a";
+ private static final String IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA =
+ "030c316ce55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ + "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ + "59eb4e81";
+ private static final String IKE_AUTH_INIT_REQUEST_CHECKSUM = "ae6e0f22abdad69ba8007d50";
+
+ private static final String IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA =
+ "2400000c010000000a50500d2700000c010000000a505050"
+ + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327"
+ + "44dfb2c12c00002c00000028010304032ad4c0a20300000c"
+ + "0100000c800e008003000008030000020000000805000000"
+ + "2d00001801000000070000100000ffff00000000ffffffff"
+ + "2900001801000000070000100000ffff00000000ffffffff"
+ + "29000008000040000000000c0000400100000001";
+ private static final String IKE_AUTH_INIT_REQUEST_PADDING = "0000000000000000000000";
+ private static final int HMAC_SHA1_CHECKSUM_LEN = 12;
+
+ private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1";
+ private static final String INTE_KEY_FROM_INIT_TO_RESP =
+ "554fbf5a05b7f511e05a30ce23d874db9ef55e51";
+
+ private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding";
+
+ // Test vectors for IKE message protected by HmacSha1 and 3DES
+ private static final String HMAC_SHA1_3DES_MSG_HEX_STRING =
+ "5837b1bd28ec424f85ddd0c609c8dbfe2e20232000000002"
+ + "00000064300000488beaf41d88544baabd95eac60269f19a"
+ + "5986295fe318ce02f65368cd957985f36b183794c4c78d35"
+ + "437762297a131a773d7f7806aaa0c590f48b9d71001f4d65"
+ + "70a44533";
+
+ private static final String HMAC_SHA1_3DES_DECRYPTED_BODY_HEX_STRING =
+ "00000028013c00241a013c001f10dac4f8b759138776091dd0f00033c5b07374726f6e675377616e";
+
+ private static final String HMAC_SHA1_3DES_MSG_ENCR_KEY =
+ "ee0fdd6d35bbdbe9eeef2f24495b6632e5047bdd8e413c87";
+ private static final String HMAC_SHA1_3DES_MSG_INTE_KEY =
+ "867a0bd019108db856cf6984fc9fb62d70c0de74";
+
+ // TODO: b/142753861 Test IKE fragment protected by AES_CBC instead of 3DES
+
+ // Test vectors for IKE fragment protected by HmacSha1 and 3DES
+ private static final String HMAC_SHA1_3DES_FRAG_HEX_STRING =
+ "939ae1251d18eb9077a99551b15c6e933520232000000001"
+ + "000000c0000000a400050005fd7c7931705af184b7be76bb"
+ + "d45a8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2"
+ + "ff6afde3f2c13ae93ce55a801f62e1a818c9003880a36bbe"
+ + "986fe6979ba233b9f4f0ddc992d06dbad5a2b998be18fae9"
+ + "47e5ccfb37775d069344e711fbf499bb289cf4cca245bd45"
+ + "0ad89d18689207759507ba18d47247e920b9e000a25a7596"
+ + "e4130929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d";
+ private static final String HMAC_SHA1_3DES_FRAG_DECRYPTED_BODY_HEX_STRING =
+ "54ebd95c572100002002000000000100040a0a0a01000300"
+ + "040808080800030004080804042c00002c00000028020304"
+ + "03cc86090a0300000c0100000c800e010003000008030000"
+ + "0200000008050000002d00001801000000070000100000ff"
+ + "ff0a0a0a010a0a0a010000001801000000070000100000ff"
+ + "ff00000000ffffffff";
+ private static final String HMAC_SHA1_3DES_FRAG_IV = "fd7c7931705af184";
+ private static final String HMAC_SHA1_3DES_FRAG_PADDING = "dcf0fa5e2b64";
+
+ private static final int HMAC_SHA1_3DES_FRAGMENT_NUM = 5;
+ private static final int HMAC_SHA1_3DES_TOTAL_FRAGMENTS = 5;
+
+ private static final String HMAC_SHA1_3DES_FRAG_ENCR_KEY =
+ "6BBF6CB3526D6492F4DA0AF45E9B9FD3E1FF534280352073";
+ private static final String HMAC_SHA1_3DES_FRAG_INTE_KEY =
+ "293449E8E518060780B9C06F15838A06EEF57814";
+
+ // Test vectors for IKE message protected by AES_GCM_16
+ private static final String AES_GCM_MSG_HEX_STRING =
+ "77c708b4523e39a471dc683c1d4f21362e20230800000005"
+ + "0000006127000045fbd69d9ee2dafc5e7c03a0106761065b2"
+ + "8fa8d11aed6046f7f8af117e44da7635be6e0dfafcb0a387c"
+ + "53fb46ba5d6fa9509161915929de97b7fbe23dc65723b0fe";
+ private static final String AES_GCM_MSG_DECRYPTED_BODY_HEX_STRING =
+ "000000280200000033233837e909ec805d56151bef5b1fa9b8e25b32419c9b3fc96ee699ec29d501";
+ private static final String AES_GCM_MSG_IV = "fbd69d9ee2dafc5e";
+ private static final String AES_GCM_MSG_ENCR_KEY =
+ "7C04513660DEC572D896105254EF92608054F8E6EE19E79CE52AB8697B2B5F2C2AA90C29";
+
+ // Test vectors for IKE fragment protected by AES_GCM_16
+ private static final String AES_GCM_FRAG_HEX_STRING =
+ "77c708b4523e39a471dc683c1d4f213635202320000000010"
+ + "0000113000000f7000200026faf9e5c04c67571871681d443"
+ + "01489f99fd78d318b0517a5a99bf6a3e1770f43d7d997c9e0"
+ + "d186038d16df3fd525eda821f80b3a40fc6bce397ac67539e"
+ + "40042919a5e9af38c70881d092a8571f0e131f594c0e8d6b8"
+ + "4ea116f0c95619439b0a267b35bc47dac72bbfb3d3776feb3"
+ + "86d7d4f819b0248f52f60bf4371ab6384e37819a9685c27d8"
+ + "e41abe30cd6f60905dd5c05c351ec0a1fcf9b99360161d2f3"
+ + "4dcf6401829df9392121d88e2201d279200e25d750678af6a"
+ + "7f4892a5c8d4a7358ec50cdf12cfa7652488f756ba6d07441"
+ + "e9a27aad3976ac8a705ff796857cb2df9ce360c3992e0285b"
+ + "34834255b06";
+ private static final String AES_GCM_FRAG_DECRYPTED_BODY_HEX_STRING =
+ "0fce6e996f4936ec8db8097339c371c686be75f4bed3f259c"
+ + "14d39c3ad90cb864085c6375f75b724d9f9daa8e7b22a106a"
+ + "488bc48c081997b7416fd33b146882e51ff6a640edf760212"
+ + "7f2454d502e92262ba3dd07cff52ee1bb1ea85f582db41a68"
+ + "aaf6dace362e5d8b10cfeb65eebc7572690e2c415a11cae57"
+ + "020810cf7aa56d9f2d5c2be3a633f8e4c6af5483a2b1f05bd"
+ + "4340ab551ddf7f51def57eaf5a37793ff6aa1e1ec288a2adf"
+ + "a647c369f15efa61a619966a320f24e1765c0e00c5ed394aa"
+ + "ef14512032b005827c000000090100000501";
+ private static final String AES_GCM_FRAG_IV = "6faf9e5c04c67571";
+
+ private static final int AES_GCM_FRAGMENT_NUM = 2;
+ private static final int AES_GCM_TOTAL_FRAGMENTS = 2;
+
+ private static final String AES_GCM_FRAG_ENCR_KEY =
+ "955ED949D6F18857220E97B17D9285C830A39F8D4DC46AB43943668093C62A3D66664F8C";
+
+ private static final int ENCRYPTED_BODY_SK_OFFSET =
+ IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH;
+ private static final int ENCRYPTED_BODY_SKF_OFFSET =
+ ENCRYPTED_BODY_SK_OFFSET + IkeSkfPayload.SKF_HEADER_LEN;
+
+ private IkeNormalModeCipher mAesCbcCipher;
+ private byte[] mAesCbcKey;
+
+ private IkeMacIntegrity mHmacSha1IntegrityMac;
+ private byte[] mHmacSha1IntegrityKey;
+
+ private byte[] mDataToPadAndEncrypt;
+ private byte[] mDataToAuthenticate;
+ private byte[] mEncryptedPaddedData;
+ private byte[] mIkeMessage;
+
+ private byte[] mChecksum;
+ private byte[] mIv;
+ private byte[] mPadding;
+
+ private IkeNormalModeCipher m3DesCipher;
+
+ private IkeCombinedModeCipher mAesGcm16Cipher;
+
+ private byte[] mAesGcmMsgKey;
+ private byte[] mAesGcmMsg;
+ private byte[] mAesGcmUnencryptedData;
+
+ private byte[] mAesGcmFragKey;
+ private byte[] mAesGcmFragMsg;
+ private byte[] mAesGcmFragUnencryptedData;
+
+ @Before
+ public void setUp() throws Exception {
+ mDataToPadAndEncrypt =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_UNENCRYPTED_DATA);
+ String hexStringToAuthenticate =
+ IKE_AUTH_INIT_REQUEST_HEADER
+ + IKE_AUTH_INIT_REQUEST_SK_HEADER
+ + IKE_AUTH_INIT_REQUEST_IV
+ + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA;
+ mDataToAuthenticate = TestUtils.hexStringToByteArray(hexStringToAuthenticate);
+ mEncryptedPaddedData =
+ TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA);
+ mIkeMessage =
+ TestUtils.hexStringToByteArray(
+ IKE_AUTH_INIT_REQUEST_HEADER
+ + IKE_AUTH_INIT_REQUEST_SK_HEADER
+ + IKE_AUTH_INIT_REQUEST_IV
+ + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA
+ + IKE_AUTH_INIT_REQUEST_CHECKSUM);
+
+ mChecksum = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_CHECKSUM);
+ mIv = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_IV);
+ mPadding = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_PADDING);
+
+ m3DesCipher =
+ (IkeNormalModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES),
+ IkeMessage.getSecurityProvider());
+
+ mAesCbcCipher =
+ (IkeNormalModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
+ SaProposal.KEY_LEN_AES_128),
+ IkeMessage.getSecurityProvider());
+ mAesCbcKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
+
+ mHmacSha1IntegrityMac =
+ IkeMacIntegrity.create(
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
+ IkeMessage.getSecurityProvider());
+ mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTE_KEY_FROM_INIT_TO_RESP);
+
+ mAesGcm16Cipher =
+ (IkeCombinedModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16,
+ SaProposal.KEY_LEN_AES_256),
+ IkeMessage.getSecurityProvider());
+
+ mAesGcmMsgKey = TestUtils.hexStringToByteArray(AES_GCM_MSG_ENCR_KEY);
+ mAesGcmMsg = TestUtils.hexStringToByteArray(AES_GCM_MSG_HEX_STRING);
+ mAesGcmUnencryptedData =
+ TestUtils.hexStringToByteArray(AES_GCM_MSG_DECRYPTED_BODY_HEX_STRING);
+
+ mAesGcmFragKey = TestUtils.hexStringToByteArray(AES_GCM_FRAG_ENCR_KEY);
+ mAesGcmFragMsg = TestUtils.hexStringToByteArray(AES_GCM_FRAG_HEX_STRING);
+ mAesGcmFragUnencryptedData =
+ TestUtils.hexStringToByteArray(AES_GCM_FRAG_DECRYPTED_BODY_HEX_STRING);
+ }
+
+ @Test
+ public void testValidateChecksum() throws Exception {
+ IkeEncryptedPayloadBody.validateInboundChecksumOrThrow(
+ mDataToAuthenticate, mHmacSha1IntegrityMac, mHmacSha1IntegrityKey, mChecksum);
+ }
+
+ @Test
+ public void testThrowForInvalidChecksum() throws Exception {
+ byte[] dataToAuthenticate = Arrays.copyOf(mDataToAuthenticate, mDataToAuthenticate.length);
+ dataToAuthenticate[0]++;
+
+ try {
+ IkeEncryptedPayloadBody.validateInboundChecksumOrThrow(
+ dataToAuthenticate, mHmacSha1IntegrityMac, mHmacSha1IntegrityKey, mChecksum);
+ fail("Expected GeneralSecurityException due to mismatched checksum.");
+ } catch (GeneralSecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testCalculatePaddingPlaintextShorterThanBlockSize() throws Exception {
+ int blockSize = 16;
+ int plainTextLength = 15;
+ int expectedPadLength = 0;
+
+ byte[] calculatedPadding =
+ IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize);
+ assertEquals(expectedPadLength, calculatedPadding.length);
+ }
+
+ @Test
+ public void testCalculatePaddingPlaintextInBlockSize() throws Exception {
+ int blockSize = 16;
+ int plainTextLength = 16;
+ int expectedPadLength = 15;
+
+ byte[] calculatedPadding =
+ IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize);
+ assertEquals(expectedPadLength, calculatedPadding.length);
+ }
+
+ @Test
+ public void testCalculatePaddingPlaintextLongerThanBlockSize() throws Exception {
+ int blockSize = 16;
+ int plainTextLength = 17;
+ int expectedPadLength = 14;
+
+ byte[] calculatedPadding =
+ IkeEncryptedPayloadBody.calculatePadding(plainTextLength, blockSize);
+ assertEquals(expectedPadLength, calculatedPadding.length);
+ }
+
+ @Test
+ public void testEncrypt() throws Exception {
+ byte[] calculatedData =
+ IkeEncryptedPayloadBody.normalModeEncrypt(
+ mDataToPadAndEncrypt, mAesCbcCipher, mAesCbcKey, mIv, mPadding);
+
+ assertArrayEquals(mEncryptedPaddedData, calculatedData);
+ }
+
+ @Test
+ public void testDecrypt() throws Exception {
+ byte[] calculatedPlainText =
+ IkeEncryptedPayloadBody.normalModeDecrypt(
+ mEncryptedPaddedData, mAesCbcCipher, mAesCbcKey, mIv);
+
+ assertArrayEquals(mDataToPadAndEncrypt, calculatedPlainText);
+ }
+
+ @Test
+ public void testBuildAndEncodeOutboundIkeEncryptedPayloadBody() throws Exception {
+ IkeHeader ikeHeader = new IkeHeader(mIkeMessage);
+
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_ID_INITIATOR,
+ new byte[0] /*skfHeader*/,
+ mDataToPadAndEncrypt,
+ mHmacSha1IntegrityMac,
+ mAesCbcCipher,
+ mHmacSha1IntegrityKey,
+ mAesCbcKey,
+ mIv,
+ mPadding);
+
+ byte[] expectedEncodedData =
+ TestUtils.hexStringToByteArray(
+ IKE_AUTH_INIT_REQUEST_IV
+ + IKE_AUTH_INIT_REQUEST_ENCRYPT_PADDED_DATA
+ + IKE_AUTH_INIT_REQUEST_CHECKSUM);
+ assertArrayEquals(expectedEncodedData, payloadBody.encode());
+ }
+
+ @Test
+ public void testAuthAndDecodeHmacSha1AesCbc() throws Exception {
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ mIkeMessage,
+ ENCRYPTED_BODY_SK_OFFSET,
+ mHmacSha1IntegrityMac,
+ mAesCbcCipher,
+ mHmacSha1IntegrityKey,
+ mAesCbcKey);
+
+ assertArrayEquals(mDataToPadAndEncrypt, payloadBody.getUnencryptedData());
+ }
+
+ @Test
+ public void testAuthAndDecodeHmacSha13Des() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_HEX_STRING);
+ byte[] expectedDecryptedData =
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_DECRYPTED_BODY_HEX_STRING);
+
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ message,
+ ENCRYPTED_BODY_SK_OFFSET,
+ mHmacSha1IntegrityMac,
+ m3DesCipher,
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_INTE_KEY),
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_MSG_ENCR_KEY));
+
+ assertArrayEquals(expectedDecryptedData, payloadBody.getUnencryptedData());
+ }
+
+ @Test
+ public void testBuildAndEncodeWithHmacSha13Des() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_HEX_STRING);
+ IkeHeader ikeHeader = new IkeHeader(message);
+
+ byte[] skfHeaderBytes =
+ IkeSkfPayload.encodeSkfHeader(
+ HMAC_SHA1_3DES_FRAGMENT_NUM, HMAC_SHA1_3DES_TOTAL_FRAGMENTS);
+
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_NO_NEXT,
+ skfHeaderBytes,
+ TestUtils.hexStringToByteArray(
+ HMAC_SHA1_3DES_FRAG_DECRYPTED_BODY_HEX_STRING),
+ mHmacSha1IntegrityMac,
+ m3DesCipher,
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_INTE_KEY),
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_ENCR_KEY),
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_IV),
+ TestUtils.hexStringToByteArray(HMAC_SHA1_3DES_FRAG_PADDING));
+
+ byte[] expectedEncodedData =
+ Arrays.copyOfRange(message, ENCRYPTED_BODY_SKF_OFFSET, message.length);
+
+ assertArrayEquals(expectedEncodedData, payloadBody.encode());
+ }
+
+ @Test
+ public void testAuthAndDecodeFullMsgWithAesGcm() throws Exception {
+ IkeEncryptedPayloadBody encryptedBody =
+ new IkeEncryptedPayloadBody(
+ mAesGcmMsg,
+ ENCRYPTED_BODY_SK_OFFSET,
+ null /*integrityMac*/,
+ mAesGcm16Cipher,
+ null /*integrityKey*/,
+ mAesGcmMsgKey);
+
+ assertArrayEquals(mAesGcmUnencryptedData, encryptedBody.getUnencryptedData());
+ }
+
+ @Test
+ public void testBuildAndEncodeMsgWithAesGcm() throws Exception {
+ IkeHeader ikeHeader = new IkeHeader(mAesGcmMsg);
+
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ new byte[0],
+ mAesGcmUnencryptedData,
+ null /*integrityMac*/,
+ mAesGcm16Cipher,
+ null /*integrityKey*/,
+ mAesGcmMsgKey,
+ TestUtils.hexStringToByteArray(AES_GCM_MSG_IV),
+ new byte[0] /*padding*/);
+
+ byte[] expectedEncodedData =
+ Arrays.copyOfRange(mAesGcmMsg, ENCRYPTED_BODY_SK_OFFSET, mAesGcmMsg.length);
+
+ assertArrayEquals(expectedEncodedData, payloadBody.encode());
+ }
+
+ @Test
+ public void testAuthAndDecodeFragMsgWithAesGcm() throws Exception {
+ IkeEncryptedPayloadBody encryptedBody =
+ new IkeEncryptedPayloadBody(
+ mAesGcmFragMsg,
+ ENCRYPTED_BODY_SKF_OFFSET,
+ null /*integrityMac*/,
+ mAesGcm16Cipher,
+ null /*integrityKey*/,
+ mAesGcmFragKey);
+
+ assertArrayEquals(mAesGcmFragUnencryptedData, encryptedBody.getUnencryptedData());
+ }
+
+ @Test
+ public void testBuildAndEncodeFragMsgWithAesGcm() throws Exception {
+ IkeHeader ikeHeader = new IkeHeader(mAesGcmFragMsg);
+ byte[] skfHeaderBytes =
+ IkeSkfPayload.encodeSkfHeader(AES_GCM_FRAGMENT_NUM, AES_GCM_TOTAL_FRAGMENTS);
+
+ IkeEncryptedPayloadBody payloadBody =
+ new IkeEncryptedPayloadBody(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_NO_NEXT,
+ skfHeaderBytes,
+ mAesGcmFragUnencryptedData,
+ null /*integrityMac*/,
+ mAesGcm16Cipher,
+ null /*integrityKey*/,
+ mAesGcmFragKey,
+ TestUtils.hexStringToByteArray(AES_GCM_FRAG_IV),
+ new byte[0] /*padding*/);
+
+ byte[] expectedEncodedData =
+ Arrays.copyOfRange(
+ mAesGcmFragMsg, ENCRYPTED_BODY_SKF_OFFSET, mAesGcmFragMsg.length);
+
+ assertArrayEquals(expectedEncodedData, payloadBody.encode());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java
new file mode 100644
index 00000000..4592815d
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeHeaderTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidMajorVersionException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public final class IkeHeaderTest {
+ private static final String IKE_HEADER_HEX_STRING =
+ "8f54bf6d8b48e6e10000000000000000212022080000000000000150";
+ private static final String IKE_SA_INIT_RAW_PACKET =
+ "8f54bf6d8b48e6e100000000000000002120220800000000"
+ + "00000150220000300000002c010100040300000c0100000c"
+ + "800e00800300000803000002030000080400000200000008"
+ + "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ + "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ + "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ + "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
+
+ private static final String IKE_INITIATOR_SPI = "8f54bf6d8b48e6e1";
+ private static final String IKE_RESPODNER_SPI = "0000000000000000";
+
+ @IkePayload.PayloadType
+ private static final byte IKE_FIRST_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_SA;
+
+ private static final byte IKE_MAJOR_VERSION = 2;
+ private static final byte IKE_MINOR_VERSION = 0;
+
+ @IkeHeader.ExchangeType
+ private static final int IKE_EXCHANGE_TYPE = IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT;
+
+ private static final int IKE_MSG_ID = 0;
+ private static final int IKE_MSG_LENGTH = 336;
+ private static final int IKE_MSG_BODY_LENGTH = IKE_MSG_LENGTH - IkeHeader.IKE_HEADER_LENGTH;
+
+ // Byte offsets of version field in IKE message header.
+ private static final int VERSION_OFFSET = 17;
+ // Byte offsets of exchange type in IKE message header.
+ private static final int EXCHANGE_TYPE_OFFSET = 18;
+ // Byte offsets of message length in IKE message header.
+ private static final int MESSAGE_LENGTH_OFFSET = 24;
+
+ @Test
+ public void testDecodeIkeHeader() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ IkeHeader header = new IkeHeader(inputPacket);
+
+ assertEquals(IKE_MSG_LENGTH, inputPacket.length);
+
+ long initSpi = Long.parseUnsignedLong(IKE_INITIATOR_SPI, 16);
+ assertEquals(initSpi, header.ikeInitiatorSpi);
+ long respSpi = Long.parseUnsignedLong(IKE_RESPODNER_SPI, 16);
+ assertEquals(respSpi, header.ikeResponderSpi);
+
+ assertEquals(IKE_FIRST_PAYLOAD_TYPE, header.nextPayloadType);
+ assertEquals(IKE_MAJOR_VERSION, header.majorVersion);
+ assertEquals(IKE_MINOR_VERSION, header.minorVersion);
+ assertEquals(IKE_EXCHANGE_TYPE, header.exchangeType);
+ assertFalse(header.isResponseMsg);
+ assertTrue(header.fromIkeInitiator);
+ assertEquals(IKE_MSG_ID, header.messageId);
+ assertEquals(IKE_MSG_LENGTH, header.getInboundMessageLength());
+ }
+
+ @Test
+ public void testDecodeIkeHeaderWithInvalidMajorVersion() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set major version 3.
+ inputPacket[VERSION_OFFSET] = (byte) 0x30;
+ // Set Exchange type 0
+ inputPacket[EXCHANGE_TYPE_OFFSET] = (byte) 0x00;
+
+ InvalidMajorVersionException exception =
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(
+ inputPacket, InvalidMajorVersionException.class);
+
+ assertEquals(3, exception.getMajorVerion());
+ }
+
+ @Test
+ public void testDecodeIkeHeaderWithInvalidExchangeType() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set Exchange type 0
+ inputPacket[EXCHANGE_TYPE_OFFSET] = (byte) 0x00;
+
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testDecodeIkeHeaderWithInvalidPacketLength() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set Exchange type 0
+ inputPacket[MESSAGE_LENGTH_OFFSET] = (byte) 0x01;
+
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testEncodeIkeHeader() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ IkeHeader header = new IkeHeader(inputPacket);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH);
+ header.encodeToByteBuffer(byteBuffer, IKE_MSG_BODY_LENGTH);
+
+ byte[] expectedPacket = TestUtils.hexStringToByteArray(IKE_HEADER_HEX_STRING);
+ assertArrayEquals(expectedPacket, byteBuffer.array());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java
new file mode 100644
index 00000000..75552b31
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeIdPayloadTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeFqdnIdentification;
+import android.net.ipsec.ike.IkeIdentification;
+import android.net.ipsec.ike.IkeIpv4AddrIdentification;
+import android.net.ipsec.ike.IkeIpv6AddrIdentification;
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeRfc822AddrIdentification;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+
+import org.junit.Test;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+public final class IkeIdPayloadTest {
+
+ private static final String IPV4_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING =
+ "2700000c01000000c0000264";
+ private static final String IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING = "01000000c0000264";
+ private static final String IPV4_ADDR_STRING = "192.0.2.100";
+
+ private static final String IPV6_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING =
+ "27000018050000000000200100000db80000000000000001";
+ private static final String IPV6_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING =
+ "050000000000200100000db80000000000000001";
+ private static final String IPV6_ADDR_STRING = "0:2001:0:db8::1";
+
+ private static final String FQDN_ID_PAYLOAD_HEX_STRING =
+ "2500001702000000696B652E616E64726F69642E6E6574";
+ private static final String FQDN_ID_PAYLOAD_BODY_HEX_STRING =
+ "02000000696B652E616E64726F69642E6E6574";
+ private static final String FQDN = "ike.android.net";
+
+ private static final String RFC822_ADDR_ID_PAYLOAD_HEX_STRING =
+ "2500001e03000000616e64726f6964696b65406578616d706c652e636f6d";
+ private static final String RFC822_ADDR_ID_PAYLOAD_BODY_HEX_STRING =
+ "03000000616e64726f6964696b65406578616d706c652e636f6d";
+ private static final String RFC822_NAME = "androidike@example.com";
+
+ private static final String KEY_ID_PAYLOAD_HEX_STRING =
+ "250000170b000000616E64726F6964496B654B65794964";
+ private static final String KEY_ID_PAYLOAD_BODY_HEX_STRING =
+ "0b000000616E64726F6964496B654B65794964";
+ private static final byte[] KEY_ID = "androidIkeKeyId".getBytes();
+
+ private static final int ID_TYPE_OFFSET = 0;
+
+ @Test
+ public void testDecodeIpv4AddrIdPayload() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING);
+ IkeIdPayload payload = new IkeIdPayload(false, inputPacket, false);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType);
+ assertEquals(IkeIdentification.ID_TYPE_IPV4_ADDR, payload.ikeId.idType);
+ IkeIpv4AddrIdentification ikeId = (IkeIpv4AddrIdentification) payload.ikeId;
+ Inet4Address expectedAddr = (Inet4Address) Inet4Address.getByName(IPV4_ADDR_STRING);
+ assertEquals(expectedAddr, ikeId.ipv4Address);
+ }
+
+ @Test
+ public void testDecodeIpv6AddrIdPayload() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(IPV6_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING);
+ IkeIdPayload payload = new IkeIdPayload(false, inputPacket, false);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType);
+ assertEquals(IkeIdentification.ID_TYPE_IPV6_ADDR, payload.ikeId.idType);
+ IkeIpv6AddrIdentification ikeId = (IkeIpv6AddrIdentification) payload.ikeId;
+ Inet6Address expectedAddr = (Inet6Address) Inet6Address.getByName(IPV6_ADDR_STRING);
+ assertEquals(expectedAddr, ikeId.ipv6Address);
+ }
+
+ @Test
+ public void testDecodeFqdnIdPayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(FQDN_ID_PAYLOAD_BODY_HEX_STRING);
+ IkeIdPayload payload =
+ new IkeIdPayload(false /*critical*/, inputPacket, false /*isInitiator*/);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_ID_RESPONDER, payload.payloadType);
+ assertArrayEquals(inputPacket, payload.getEncodedPayloadBody());
+ assertEquals(IkeIdentification.ID_TYPE_FQDN, payload.ikeId.idType);
+ IkeFqdnIdentification ikeId = (IkeFqdnIdentification) payload.ikeId;
+ assertEquals(FQDN, ikeId.fqdn);
+ }
+
+ @Test
+ public void testDecodeRfc822AddrIdPayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(RFC822_ADDR_ID_PAYLOAD_BODY_HEX_STRING);
+ IkeIdPayload payload =
+ new IkeIdPayload(false /*critical*/, inputPacket, true /*isInitiator*/);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, payload.payloadType);
+ assertEquals(IkeIdentification.ID_TYPE_RFC822_ADDR, payload.ikeId.idType);
+ IkeRfc822AddrIdentification ikeId = (IkeRfc822AddrIdentification) payload.ikeId;
+ assertEquals(RFC822_NAME, ikeId.rfc822Name);
+ }
+
+ @Test
+ public void testDecodeKeyIdPayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(KEY_ID_PAYLOAD_BODY_HEX_STRING);
+ IkeIdPayload payload =
+ new IkeIdPayload(false /*critical*/, inputPacket, true /*isInitiator*/);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, payload.payloadType);
+ assertEquals(IkeIdentification.ID_TYPE_KEY_ID, payload.ikeId.idType);
+ IkeKeyIdIdentification ikeId = (IkeKeyIdIdentification) payload.ikeId;
+ assertArrayEquals(KEY_ID, ikeId.keyId);
+ }
+
+ @Test
+ public void testDecodeUnsupportedIdType() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_BODY_HEX_STRING);
+ inputPacket[ID_TYPE_OFFSET] = 0;
+
+ try {
+ new IkeIdPayload(false, inputPacket, true);
+ fail("Expected AuthenticationFailedException: ID Type is unsupported.");
+ } catch (AuthenticationFailedException expected) {
+ }
+ }
+
+ @Test
+ public void testConstructAndEncodeIpv4AddrIdPayload() throws Exception {
+ Inet4Address ipv4Address = (Inet4Address) Inet4Address.getByName(IPV4_ADDR_STRING);
+ IkeIdPayload payload = new IkeIdPayload(false, new IkeIpv4AddrIdentification(ipv4Address));
+
+ ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_AUTH, inputBuffer);
+
+ byte[] expectedBytes =
+ TestUtils.hexStringToByteArray(IPV4_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING);
+ assertArrayEquals(expectedBytes, inputBuffer.array());
+ }
+
+ @Test
+ public void testConstructAndEncodeIpv6AddrIdPayload() throws Exception {
+ Inet6Address ipv6Address = (Inet6Address) Inet6Address.getByName(IPV6_ADDR_STRING);
+ IkeIdPayload payload = new IkeIdPayload(false, new IkeIpv6AddrIdentification(ipv6Address));
+
+ ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_AUTH, inputBuffer);
+
+ byte[] expectedBytes =
+ TestUtils.hexStringToByteArray(IPV6_ADDR_ID_PAYLOAD_RESPONDER_HEX_STRING);
+ assertArrayEquals(expectedBytes, inputBuffer.array());
+ }
+
+ @Test
+ public void testConstructAndEncodeFqdnIdPayload() throws Exception {
+ IkeIdPayload payload =
+ new IkeIdPayload(false /*isInitiator*/, new IkeFqdnIdentification(FQDN));
+
+ ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(FQDN_ID_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedBytes, inputBuffer.array());
+ }
+
+ @Test
+ public void testConstructAndEncodeRfc822AddrIdPayload() throws Exception {
+ IkeIdPayload payload =
+ new IkeIdPayload(
+ true /*isInitiator*/, new IkeRfc822AddrIdentification(RFC822_NAME));
+
+ ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(RFC822_ADDR_ID_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedBytes, inputBuffer.array());
+ }
+
+ @Test
+ public void testConstructAndEncodeKeyIdPayload() throws Exception {
+ IkeIdPayload payload =
+ new IkeIdPayload(true /*isInitiator*/, new IkeKeyIdIdentification(KEY_ID));
+
+ ByteBuffer inputBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_CERT, inputBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(KEY_ID_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedBytes, inputBuffer.array());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java
new file mode 100644
index 00000000..f5046dca
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeKePayloadTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.IkeDhParams;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.utils.BigIntegerUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+import javax.crypto.spec.DHPrivateKeySpec;
+
+public final class IkeKePayloadTest {
+ private static final String KE_PAYLOAD_GENERIC_HEADER = "28000088";
+ private static final String KE_PAYLOAD_RAW_PACKET =
+ "00020000b4a2faf4bb54878ae21d638512ece55d9236fc50"
+ + "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa"
+ + "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd"
+ + "92f46bef84f0be7db860351843858f8acf87056e272377f7"
+ + "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ + "6bbeb08214c7071376079587";
+
+ private static final boolean CRITICAL_BIT = false;
+
+ @IkePayload.PayloadType
+ private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_NONCE;
+
+ private static final int EXPECTED_DH_GROUP = SaProposal.DH_GROUP_1024_BIT_MODP;
+
+ private static final int EXPECTED_KE_DATA_LEN = 128;
+
+ private static final String KEY_EXCHANGE_DATA_RAW_PACKET =
+ "b4a2faf4bb54878ae21d638512ece55d9236fc5046ab6cef"
+ + "82220f421f3ce6361faf36564ecb6d28798a94aad7b2b4b6"
+ + "03ddeaaa5630adb9ece8ac37534036040610ebdd92f46bef"
+ + "84f0be7db860351843858f8acf87056e272377f70c9f2d81"
+ + "e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c26bbeb082"
+ + "14c7071376079587";
+
+ private static final String PRIME_1024_BIT_MODP_160_SUBGROUP =
+ "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
+ + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0"
+ + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
+ + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0"
+ + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
+ + "DF1FB2BC2E4A4371";
+ private static final String GENERATOR_1024_BIT_MODP_160_SUBGROUP =
+ "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
+ + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213"
+ + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
+ + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A"
+ + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
+ + "855E6EEB22B3B2E5";
+ private static final String PRIVATE_KEY_LOCAL = "B9A3B3AE8FEFC1A2930496507086F8455D48943E";
+ private static final String PUBLIC_KEY_REMOTE =
+ "717A6CB053371FF4A3B932941C1E5663F861A1D6AD34AE66"
+ + "576DFB98F6C6CBF9DDD5A56C7833F6BCFDFF095582AD868E"
+ + "440E8D09FD769E3CECCDC3D3B1E4CFA057776CAAF9739B6A"
+ + "9FEE8E7411F8D6DAC09D6A4EDB46CC2B5D5203090EAE6126"
+ + "311E53FD2C14B574E6A3109A3DA1BE41BDCEAA186F5CE067"
+ + "16A2B6A07B3C33FE";
+ private static final String EXPECTED_SHARED_KEY =
+ "5C804F454D30D9C4DF85271F93528C91DF6B48AB5F80B3B5"
+ + "9CAAC1B28F8ACBA9CD3E39F3CB614525D9521D2E644C53B8"
+ + "07B810F340062F257D7D6FBFE8D5E8F072E9B6E9AFDA9413"
+ + "EAFB2E8B0699B1FB5A0CACEDDEAEAD7E9CFBB36AE2B42083"
+ + "5BD83A19FB0B5E96BF8FA4D09E345525167ECD9155416F46"
+ + "F408ED31B63C6E6D";
+ private static final String KEY_EXCHANGE_ALGORITHM = "DH";
+
+ private DHPrivateKeySpec mPrivateKeySpec;
+
+ @Before
+ public void setUp() throws Exception {
+ BigInteger primeValue =
+ BigIntegerUtils.unsignedHexStringToBigInteger(PRIME_1024_BIT_MODP_160_SUBGROUP);
+ BigInteger baseGenValue =
+ BigIntegerUtils.unsignedHexStringToBigInteger(GENERATOR_1024_BIT_MODP_160_SUBGROUP);
+ BigInteger privateKeyValue =
+ BigIntegerUtils.unsignedHexStringToBigInteger(PRIVATE_KEY_LOCAL);
+ mPrivateKeySpec = new DHPrivateKeySpec(privateKeyValue, primeValue, baseGenValue);
+ }
+
+ @Test
+ public void testDecodeIkeKePayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(KE_PAYLOAD_RAW_PACKET);
+
+ IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket);
+
+ assertFalse(payload.isOutbound);
+ assertEquals(EXPECTED_DH_GROUP, payload.dhGroup);
+
+ byte[] keyExchangeData = TestUtils.hexStringToByteArray(KEY_EXCHANGE_DATA_RAW_PACKET);
+ assertEquals(keyExchangeData.length, payload.keyExchangeData.length);
+ for (int i = 0; i < keyExchangeData.length; i++) {
+ assertEquals(keyExchangeData[i], payload.keyExchangeData[i]);
+ }
+ }
+
+ @Test
+ public void testDecodeIkeKePayloadWithInvalidKeData() throws Exception {
+ // Cut bytes of KE data from original KE payload
+ String badKeyPayloadPacket =
+ KE_PAYLOAD_RAW_PACKET.substring(0, KE_PAYLOAD_RAW_PACKET.length() - 2);
+ byte[] inputPacket = TestUtils.hexStringToByteArray(badKeyPayloadPacket);
+
+ try {
+ IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket);
+ fail("Expected InvalidSyntaxException: KE data length doesn't match its DH group type");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeIkeKePayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(KE_PAYLOAD_RAW_PACKET);
+ IkeKePayload payload = new IkeKePayload(CRITICAL_BIT, inputPacket);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(NEXT_PAYLOAD_TYPE, byteBuffer);
+
+ byte[] expectedKePayload =
+ TestUtils.hexStringToByteArray(KE_PAYLOAD_GENERIC_HEADER + KE_PAYLOAD_RAW_PACKET);
+ assertArrayEquals(expectedKePayload, byteBuffer.array());
+ }
+
+ @Test
+ public void testGetIkeKePayload() throws Exception {
+ IkeKePayload payload = new IkeKePayload(SaProposal.DH_GROUP_1024_BIT_MODP);
+
+ // Test DHPrivateKeySpec
+ assertTrue(payload.isOutbound);
+ DHPrivateKeySpec privateKeySpec = payload.localPrivateKey;
+
+ BigInteger primeValue = privateKeySpec.getP();
+ BigInteger expectedPrimeValue = new BigInteger(IkeDhParams.PRIME_1024_BIT_MODP, 16);
+ assertEquals(0, expectedPrimeValue.compareTo(primeValue));
+
+ BigInteger genValue = privateKeySpec.getG();
+ BigInteger expectedGenValue = BigInteger.valueOf(IkeDhParams.BASE_GENERATOR_MODP);
+ assertEquals(0, expectedGenValue.compareTo(genValue));
+
+ // Test IkeKePayload
+ assertEquals(EXPECTED_DH_GROUP, payload.dhGroup);
+ assertEquals(EXPECTED_KE_DATA_LEN, payload.keyExchangeData.length);
+ }
+
+ // Since we didn't find test data for DH group types supported in current IKE library, we use
+ // test data for "1024-bit MODP Group with 160-bit Prime Order Subgroup" from RFC 5114. The main
+ // difference is that it uses weaker Prime and Generator values and requires more complicated
+ // recipient test in real Key Exchange process. But it is suitable for testing.
+ @Test
+ public void testGetSharedkey() throws Exception {
+ byte[] remotePublicKey = TestUtils.hexStringToByteArray(PUBLIC_KEY_REMOTE);
+ byte[] sharedKeyBytes = IkeKePayload.getSharedKey(mPrivateKeySpec, remotePublicKey);
+
+ byte[] expectedSharedKeyBytes = TestUtils.hexStringToByteArray(EXPECTED_SHARED_KEY);
+ assertTrue(Arrays.equals(expectedSharedKeyBytes, sharedKeyBytes));
+ }
+
+ @Test
+ public void testGetSharedkeyWithInvalidRemoteKey() throws Exception {
+ byte[] remotePublicKey = TestUtils.hexStringToByteArray(PRIME_1024_BIT_MODP_160_SUBGROUP);
+
+ try {
+ byte[] sharedKeyBytes = IkeKePayload.getSharedKey(mPrivateKeySpec, remotePublicKey);
+ fail("Expected to fail because of invalid remote public key.");
+ } catch (GeneralSecurityException expected) {
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java
new file mode 100644
index 00000000..38b41527
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeMessageTest.java
@@ -0,0 +1,935 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_OK;
+import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_PROTECTED_ERROR;
+import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_ID_INITIATOR;
+import static com.android.internal.net.ipsec.ike.message.IkePayload.PAYLOAD_TYPE_NO_NEXT;
+import static com.android.internal.net.ipsec.ike.message.IkeTestUtils.makeDummySkfPayload;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeInternalException;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.SaRecord.IkeSaRecord;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidMessageIdException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.UnsupportedCriticalPayloadException;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultError;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultOk;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultPartial;
+import com.android.internal.net.ipsec.ike.message.IkePayloadFactory.IIkePayloadDecoder;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import javax.crypto.IllegalBlockSizeException;
+
+public final class IkeMessageTest {
+ private static final String IKE_SA_INIT_HEADER_RAW_PACKET =
+ "8f54bf6d8b48e6e10000000000000000212022080000000000000150";
+ private static final String IKE_SA_INIT_BODY_RAW_PACKET =
+ "220000300000002c010100040300000c0100000c"
+ + "800e00800300000803000002030000080400000200000008"
+ + "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ + "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ + "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ + "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ + "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ + "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ + "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ + "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ + "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ + "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ + "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
+ private static final String IKE_SA_INIT_RAW_PACKET =
+ IKE_SA_INIT_HEADER_RAW_PACKET + IKE_SA_INIT_BODY_RAW_PACKET;
+
+ // Byte offsets of first payload type in IKE message header.
+ private static final int FIRST_PAYLOAD_TYPE_OFFSET = 16;
+ // Byte offsets of first payload's critical bit in IKE message body.
+ private static final int PAYLOAD_CRITICAL_BIT_OFFSET = 1;
+ // Byte offsets of first payload length in IKE message body.
+ private static final int FIRST_PAYLOAD_LENGTH_OFFSET = 2;
+ // Byte offsets of last payload length in IKE message body.
+ private static final int LAST_PAYLOAD_LENGTH_OFFSET = 278;
+
+ private static final String IKE_AUTH_HEADER_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec";
+ private static final String IKE_AUTH_BODY_HEX_STRING =
+ "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c"
+ + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ + "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ + "59eb4e81ae6e0f22abdad69ba8007d50";
+
+ private static final String IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING = "ae6e0f22abdad69ba8007d50";
+ private static final String IKE_AUTH_HEX_STRING =
+ IKE_AUTH_HEADER_HEX_STRING + IKE_AUTH_BODY_HEX_STRING;
+
+ private static final String IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING =
+ "2400000c010000000a50500d2700000c010000000a505050"
+ + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327"
+ + "44dfb2c12c00002c00000028010304032ad4c0a20300000c"
+ + "0100000c800e008003000008030000020000000805000000"
+ + "2d00001801000000070000100000ffff00000000ffffffff"
+ + "2900001801000000070000100000ffff00000000ffffffff"
+ + "29000008000040000000000c000040010000000100000000"
+ + "000000000000000b";
+
+ private static final String IKE_FRAG_HEX_STRING =
+ "939ae1251d18eb9077a99551b15c6e9335202320000000010000"
+ + "00c0000000a400020002fd7c7931705af184b7be76bbd45a"
+ + "8ecbb3ffd58b9438b93f67e9fe86b06229f80e9b52d2ff6a"
+ + "fde3f2c13ae93ce55a801f62e1a818c9003880a36bbe986f"
+ + "e6979ba233b9f4f0ddc992d06dbad5a2b998be18fae947e5"
+ + "ccfb37775d069344e711fbf499bb289cf4cca245bd450ad8"
+ + "9d18689207759507ba18d47247e920b9e000a25a7596e413"
+ + "0929e5cdc37d5c1b0d90bbaae946c260f4d3cf815f6d";
+ private static final String ID_INIT_PAYLOAD_HEX_STRING = "2400000c010000000a50500d";
+ private static final String ID_RESP_PAYLOAD_HEX_STRING = "0000000c010000000a505050";
+
+ private static final long INIT_SPI = 0x5f54bf6d8b48e6e1L;
+ private static final long RESP_SPI = 0x909232b3d1edcb5cL;
+ private static final String IKE_EMPTY_INFO_MSG_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c2e20252800000000"
+ + "0000004c00000030e376871750fdba9f7012446c5dc3f97a"
+ + "f83b48ba0dbc68bcc4a78136832100aa4192f251cd4d1b97"
+ + "d298e550";
+ private static final String IKE_EMPTY_INFO_MSG_IV_HEX_STRING =
+ "e376871750fdba9f7012446c5dc3f97a";
+ private static final String IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING =
+ "f83b48ba0dbc68bcc4a78136832100aa";
+ private static final String IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING = "4192f251cd4d1b97d298e550";
+
+ private static final byte[] FRAGMENT_ONE_UNENCRYPTED_DATA = "fragmentOne".getBytes();
+ private static final byte[] FRAGMENT_TWO_UNENCRYPTED_DATA = "fragmentTwo".getBytes();
+
+ private static final int TOTAL_FRAGMENTS = 2;
+ private static final int FRAGMENT_NUM_ONE = 1;
+ private static final int FRAGMENT_NUM_TWO = 2;
+
+ private static final int IKE_FRAG_EXPECTED_MESSAGE_ID = 1;
+
+ private static final int IKE_AUTH_EXPECTED_MESSAGE_ID = 1;
+ private static final int IKE_AUTH_CIPHER_IV_SIZE = 16;
+ private static final int IKE_AUTH_CIPHER_BLOCK_SIZE = 16;
+ private static final int IKE_AUTH_PAYLOAD_SIZE = 8;
+
+ private byte[] mIkeAuthPacket;
+ private byte[] mUnencryptedPaddedData;
+ private IkeHeader mIkeAuthHeader;
+
+ private IIkePayloadDecoder mSpyIkePayloadDecoder;
+
+ private IkeMacIntegrity mMockIntegrity;
+ private IkeNormalModeCipher mMockCipher;
+ private IkeSaRecord mMockIkeSaRecord;
+
+ private byte[] mIkeFragPacketOne;
+ private byte[] mIkeFragPacketTwo;
+ private IkeHeader mFragOneHeader;
+ private IkeHeader mFragTwoHeader;
+
+ private IkeSkfPayload mDummySkfPayloadOne;
+ private IkeSkfPayload mDummySkfPayloadTwo;
+
+ private static final int[] EXPECTED_IKE_INIT_PAYLOAD_LIST = {
+ IkePayload.PAYLOAD_TYPE_SA,
+ IkePayload.PAYLOAD_TYPE_KE,
+ IkePayload.PAYLOAD_TYPE_NONCE,
+ IkePayload.PAYLOAD_TYPE_NOTIFY,
+ IkePayload.PAYLOAD_TYPE_NOTIFY,
+ IkePayload.PAYLOAD_TYPE_VENDOR
+ };
+
+ class TestIkeSupportedPayload extends IkePayload {
+ TestIkeSupportedPayload(int payload, boolean critical) {
+ super(payload, critical);
+ }
+
+ @Override
+ protected void encodeToByteBuffer(@PayloadType int nextPayload, ByteBuffer byteBuffer) {
+ throw new UnsupportedOperationException(
+ "It is not supported to encode " + getTypeString());
+ }
+
+ @Override
+ protected int getPayloadLength() {
+ throw new UnsupportedOperationException(
+ "It is not supported to get payload length of " + getTypeString());
+ }
+
+ @Override
+ public String getTypeString() {
+ return "Test(" + payloadType + ")";
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mSpyIkePayloadDecoder = spy(new IkePayloadFactory.IkePayloadDecoder());
+ doAnswer(
+ (invocation) -> {
+ int payloadType = (int) invocation.getArguments()[0];
+ boolean isCritical = (boolean) invocation.getArguments()[1];
+ if (support(payloadType)) {
+ return new TestIkeSupportedPayload(payloadType, isCritical);
+ }
+ return new IkeUnsupportedPayload(payloadType, isCritical);
+ })
+ .when(mSpyIkePayloadDecoder)
+ .decodeIkePayload(anyInt(), anyBoolean(), anyBoolean(), any());
+
+ IkePayloadFactory.sDecoderInstance = mSpyIkePayloadDecoder;
+
+ mIkeAuthPacket = TestUtils.hexStringToByteArray(IKE_AUTH_HEX_STRING);
+ mUnencryptedPaddedData =
+ TestUtils.hexStringToByteArray(IKE_AUTH_UNENCRYPTED_PADDED_DATA_HEX_STRING);
+ mIkeAuthHeader = new IkeHeader(mIkeAuthPacket);
+
+ mMockIntegrity = mock(IkeMacIntegrity.class);
+ byte[] expectedChecksum =
+ TestUtils.hexStringToByteArray(IKE_AUTH_EXPECTED_CHECKSUM_HEX_STRING);
+ when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(expectedChecksum);
+ when(mMockIntegrity.getChecksumLen()).thenReturn(expectedChecksum.length);
+
+ mMockCipher = mock(IkeNormalModeCipher.class);
+ when(mMockCipher.getIvLen()).thenReturn(IKE_AUTH_CIPHER_IV_SIZE);
+ when(mMockCipher.getBlockSize()).thenReturn(IKE_AUTH_CIPHER_BLOCK_SIZE);
+ when(mMockCipher.decrypt(any(), any(), any())).thenReturn(mUnencryptedPaddedData);
+
+ mMockIkeSaRecord = mock(IkeSaRecord.class);
+ when(mMockIkeSaRecord.getInboundDecryptionKey()).thenReturn(new byte[0]);
+ when(mMockIkeSaRecord.getInboundIntegrityKey()).thenReturn(new byte[0]);
+
+ mIkeFragPacketOne = makeFragmentBytes(1 /*fragNum*/, 2 /*totalFragments*/);
+ mIkeFragPacketTwo = makeFragmentBytes(2 /*fragNum*/, 2 /*totalFragments*/);
+
+ mFragOneHeader = new IkeHeader(mIkeFragPacketOne);
+ mFragTwoHeader = new IkeHeader(mIkeFragPacketTwo);
+
+ mDummySkfPayloadOne =
+ makeDummySkfPayload(
+ FRAGMENT_ONE_UNENCRYPTED_DATA, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS);
+ mDummySkfPayloadTwo =
+ makeDummySkfPayload(
+ FRAGMENT_TWO_UNENCRYPTED_DATA, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS);
+ }
+
+ private byte[] makeFragmentBytes(int fragNum, int totalFragments) {
+ byte[] packet = TestUtils.hexStringToByteArray(IKE_FRAG_HEX_STRING);
+ ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
+ byteBuffer.get(new byte[IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH]);
+
+ byteBuffer.putShort((short) fragNum).putShort((short) totalFragments);
+ return byteBuffer.array();
+ }
+
+ @After
+ public void tearDown() {
+ IkePayloadFactory.sDecoderInstance = new IkePayloadFactory.IkePayloadDecoder();
+ }
+
+ private IkeMessage verifyDecodeResultOkAndGetMessage(
+ DecodeResult decodeResult, byte[] firstPacket) throws Exception {
+ assertEquals(DECODE_STATUS_OK, decodeResult.status);
+
+ DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+ assertNotNull(resultOk.ikeMessage);
+ assertArrayEquals(firstPacket, resultOk.firstPacket);
+
+ return resultOk.ikeMessage;
+ }
+
+ private IkeException verifyDecodeResultErrorAndGetIkeException(
+ DecodeResult decodeResult, int decodeStatus, byte[] firstPacket) throws Exception {
+ assertEquals(decodeStatus, decodeResult.status);
+
+ DecodeResultError resultError = (DecodeResultError) decodeResult;
+ assertNotNull(resultError.ikeException);
+
+ return resultError.ikeException;
+ }
+
+ @Test
+ public void testDecodeIkeMessage() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ IkeHeader header = new IkeHeader(inputPacket);
+
+ DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
+
+ IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult, inputPacket);
+
+ assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length, message.ikePayloadList.size());
+ for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length; i++) {
+ assertEquals(
+ EXPECTED_IKE_INIT_PAYLOAD_LIST[i], message.ikePayloadList.get(i).payloadType);
+ }
+ }
+
+ @Test
+ public void testDecodeMessageWithUnsupportedUncriticalPayload() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set first payload unsupported uncritical
+ inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff;
+ IkeHeader header = new IkeHeader(inputPacket);
+
+ DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
+
+ IkeMessage message = verifyDecodeResultOkAndGetMessage(decodeResult, inputPacket);
+
+ assertEquals(EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1, message.ikePayloadList.size());
+ for (int i = 0; i < EXPECTED_IKE_INIT_PAYLOAD_LIST.length - 1; i++) {
+ assertEquals(
+ EXPECTED_IKE_INIT_PAYLOAD_LIST[i + 1],
+ message.ikePayloadList.get(i).payloadType);
+ }
+ }
+
+ @Test
+ public void testThrowUnsupportedCriticalPayloadException() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set first payload unsupported critical
+ inputPacket[FIRST_PAYLOAD_TYPE_OFFSET] = (byte) 0xff;
+ inputPacket[IkeHeader.IKE_HEADER_LENGTH + PAYLOAD_CRITICAL_BIT_OFFSET] = (byte) 0x80;
+
+ UnsupportedCriticalPayloadException exception =
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(
+ inputPacket, UnsupportedCriticalPayloadException.class);
+
+ assertEquals(1, exception.payloadTypeList.size());
+ }
+
+ @Test
+ public void testDecodeMessageWithTooShortPayloadLength() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Set first payload length to 0
+ inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0;
+ inputPacket[IkeHeader.IKE_HEADER_LENGTH + FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0;
+
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testDecodeMessageWithTooLongPayloadLength() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ // Increase last payload length by one byte
+ inputPacket[IkeHeader.IKE_HEADER_LENGTH + LAST_PAYLOAD_LENGTH_OFFSET]++;
+
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testDecodeMessageWithUnexpectedBytesInTheEnd() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET + "0000");
+
+ IkeTestUtils.decodeAndVerifyUnprotectedErrorMsg(inputPacket, InvalidSyntaxException.class);
+ }
+
+ @Test
+ public void testDecodeEncryptedMessage() throws Exception {
+ DecodeResult decodeResult =
+ IkeMessage.decode(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeMessage ikeMessage = verifyDecodeResultOkAndGetMessage(decodeResult, mIkeAuthPacket);
+
+ assertEquals(IKE_AUTH_PAYLOAD_SIZE, ikeMessage.ikePayloadList.size());
+ }
+
+ @Test
+ public void testDecodeEncryptedMessageWithWrongId() throws Exception {
+ DecodeResult decodeResult =
+ IkeMessage.decode(
+ 2,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket);
+
+ assertTrue(ikeException instanceof InvalidMessageIdException);
+ }
+
+ @Test
+ public void testDecodeEncryptedMessageWithWrongChecksum() throws Exception {
+ when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(new byte[0]);
+
+ DecodeResult decodeResult =
+ IkeMessage.decode(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket);
+
+ assertTrue(
+ ((IkeInternalException) ikeException).getCause()
+ instanceof GeneralSecurityException);
+ }
+
+ @Test
+ public void testDecryptFail() throws Exception {
+ when(mMockCipher.decrypt(any(), any(), any())).thenThrow(IllegalBlockSizeException.class);
+
+ DecodeResult decodeResult =
+ IkeMessage.decode(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket);
+ assertTrue(
+ ((IkeInternalException) ikeException).getCause()
+ instanceof IllegalBlockSizeException);
+ }
+
+ @Test
+ public void testParsingErrorInEncryptedMessage() throws Exception {
+ // Set first payload length to 0
+ byte[] decryptedData =
+ Arrays.copyOfRange(mUnencryptedPaddedData, 0, mUnencryptedPaddedData.length);
+ decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET] = (byte) 0;
+ decryptedData[FIRST_PAYLOAD_LENGTH_OFFSET + 1] = (byte) 0;
+ when(mMockCipher.decrypt(any(), any(), any())).thenReturn(decryptedData);
+
+ DecodeResult decodeResult =
+ IkeMessage.decode(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ null /*collectedFragments*/);
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_PROTECTED_ERROR, mIkeAuthPacket);
+
+ assertTrue(ikeException instanceof InvalidSyntaxException);
+ }
+
+ private boolean support(int payloadType) {
+ // Supports all payload typs from 33 to 46
+ return (payloadType >= 33 && payloadType <= 46);
+ }
+
+ @Test
+ public void testAttachEncodedHeader() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(IKE_SA_INIT_RAW_PACKET);
+ byte[] ikeBodyBytes = TestUtils.hexStringToByteArray(IKE_SA_INIT_BODY_RAW_PACKET);
+ IkeHeader header = new IkeHeader(inputPacket);
+ IkeMessage message =
+ ((DecodeResultOk) IkeMessage.decode(0, header, inputPacket)).ikeMessage;
+
+ byte[] encodedIkeMessage = message.attachEncodedHeader(ikeBodyBytes);
+ assertArrayEquals(inputPacket, encodedIkeMessage);
+ }
+
+ @Test
+ public void testEncodeAndEncryptEmptyMsg() throws Exception {
+ when(mMockCipher.generateIv())
+ .thenReturn(TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_IV_HEX_STRING));
+ when(mMockCipher.encrypt(any(), any(), any()))
+ .thenReturn(
+ TestUtils.hexStringToByteArray(
+ IKE_EMPTY_INFO_MSG_ENCRYPTED_DATA_HEX_STRING));
+
+ byte[] checkSum = TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_CHECKSUM_HEX_STRING);
+ when(mMockIntegrity.getChecksumLen()).thenReturn(checkSum.length);
+ when(mMockIntegrity.generateChecksum(any(), any())).thenReturn(checkSum);
+
+ IkeHeader ikeHeader =
+ new IkeHeader(
+ INIT_SPI,
+ RESP_SPI,
+ IkePayload.PAYLOAD_TYPE_SK,
+ IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
+ true /*isResp*/,
+ true /*fromInit*/,
+ 0);
+ IkeMessage ikeMessage = new IkeMessage(ikeHeader, new LinkedList<>());
+
+ byte[][] ikeMessageBytes =
+ ikeMessage.encryptAndEncode(
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ true /*supportFragment*/,
+ 1280 /*fragSize*/);
+ byte[][] expectedBytes =
+ new byte[][] {TestUtils.hexStringToByteArray(IKE_EMPTY_INFO_MSG_HEX_STRING)};
+
+ assertArrayEquals(expectedBytes, ikeMessageBytes);
+ }
+
+ private DecodeResultPartial makeDecodeResultForFragOne(DecodeResultPartial collectedFrags) {
+ return new DecodeResultPartial(
+ mFragOneHeader,
+ mIkeFragPacketOne,
+ mDummySkfPayloadOne,
+ PAYLOAD_TYPE_AUTH,
+ collectedFrags);
+ }
+
+ private DecodeResultPartial makeDecodeResultForFragTwo(DecodeResultPartial collectedFrags) {
+ return new DecodeResultPartial(
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ mDummySkfPayloadTwo,
+ PAYLOAD_TYPE_NO_NEXT,
+ collectedFrags);
+ }
+
+ @Test
+ public void testConstructDecodePartialFirstFragArriveFirst() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragOne(null /*collectedFragments*/);
+
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartial.firstPayloadType);
+ assertArrayEquals(mIkeFragPacketOne, resultPartial.firstFragBytes);
+ assertEquals(mFragOneHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_ONE_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_ONE - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testConstructDecodePartialSecondFragArriveFirst() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/);
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertNull(resultPartial.firstFragBytes);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_TWO_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testConstructDecodeResultPartialWithCollectedFrags() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /*collectedFragments*/);
+ DecodeResultPartial resultPartialComplete =
+ makeDecodeResultForFragOne(resultPartialIncomplete);
+
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartialComplete.firstPayloadType);
+ assertArrayEquals(mIkeFragPacketOne, resultPartialComplete.firstFragBytes);
+ assertEquals(mFragTwoHeader, resultPartialComplete.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartialComplete.collectedFragsList.length);
+ assertTrue(resultPartialComplete.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testReassembleAllFrags() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragOne(null /*collectedFragments*/);
+ DecodeResultPartial resultPartialComplete =
+ makeDecodeResultForFragTwo(resultPartialIncomplete);
+
+ assertEquals(PAYLOAD_TYPE_AUTH, resultPartialIncomplete.firstPayloadType);
+ assertArrayEquals(mIkeFragPacketOne, resultPartialIncomplete.firstFragBytes);
+ assertEquals(mFragOneHeader, resultPartialIncomplete.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartialIncomplete.collectedFragsList.length);
+ assertTrue(resultPartialIncomplete.isAllFragmentsReceived());
+
+ // Verify reassembly result
+ ByteBuffer expectedBuffer =
+ ByteBuffer.allocate(
+ FRAGMENT_ONE_UNENCRYPTED_DATA.length
+ + FRAGMENT_TWO_UNENCRYPTED_DATA.length);
+ expectedBuffer.put(FRAGMENT_ONE_UNENCRYPTED_DATA).put(FRAGMENT_TWO_UNENCRYPTED_DATA);
+
+ byte[] reassembledBytes = resultPartialComplete.reassembleAllFrags();
+ assertArrayEquals(expectedBuffer.array(), reassembledBytes);
+ }
+
+ @Test
+ public void testReassembleIncompleteFragmentsThrows() throws Exception {
+ DecodeResultPartial resultPartial = makeDecodeResultForFragTwo(null /*collectedFragments*/);
+
+ assertFalse(resultPartial.isAllFragmentsReceived());
+
+ try {
+ resultPartial.reassembleAllFrags();
+ fail("Expected to fail because reassembly is not done");
+ } catch (IllegalStateException expected) {
+
+ }
+ }
+
+ private void setDecryptSkfPayload(IkeSkfPayload skf) throws Exception {
+ doReturn(skf)
+ .when(mSpyIkePayloadDecoder)
+ .decodeIkeSkPayload(
+ eq(true),
+ anyBoolean(),
+ any(),
+ eq(mMockIntegrity),
+ eq(mMockCipher),
+ any(),
+ any());
+ }
+
+ private DecodeResult decodeSkf(
+ int expectedMsgId,
+ IkeHeader header,
+ byte[] packet,
+ DecodeResultPartial collectFragments)
+ throws Exception {
+ return IkeMessage.decode(
+ expectedMsgId,
+ mMockIntegrity,
+ mMockCipher,
+ mMockIkeSaRecord,
+ header,
+ packet,
+ collectFragments);
+ }
+
+ @Test
+ public void testRcvFirstArrivedFrag() throws Exception {
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ null /* collectedFragments*/);
+
+ // Verify decoding result
+ assertTrue(decodeResult instanceof DecodeResultPartial);
+ DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertNull(resultPartial.firstFragBytes);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(TOTAL_FRAGMENTS, resultPartial.collectedFragsList.length);
+ assertArrayEquals(
+ FRAGMENT_TWO_UNENCRYPTED_DATA,
+ resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRcvLastArrivedFrag() throws Exception {
+ // Create two dummy SKF Payloads so that the complete unencrypted data is two ID payloads
+ byte[] idInitPayloadBytes = TestUtils.hexStringToByteArray(ID_INIT_PAYLOAD_HEX_STRING);
+ byte[] idRespPayloadBytes = TestUtils.hexStringToByteArray(ID_RESP_PAYLOAD_HEX_STRING);
+ IkeSkfPayload skfOne =
+ makeDummySkfPayload(idInitPayloadBytes, FRAGMENT_NUM_ONE, TOTAL_FRAGMENTS);
+ IkeSkfPayload skfTwo =
+ makeDummySkfPayload(idRespPayloadBytes, FRAGMENT_NUM_TWO, TOTAL_FRAGMENTS);
+
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ mIkeFragPacketOne,
+ skfOne,
+ PAYLOAD_TYPE_ID_INITIATOR,
+ null /* collectedFragments*/);
+
+ setDecryptSkfPayload(skfTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ resultPartialIncomplete);
+
+ // Verify fragments reassembly has been finished and complete message has been decoded.
+ assertTrue(decodeResult instanceof DecodeResultOk);
+ DecodeResultOk resultOk = (DecodeResultOk) decodeResult;
+ assertArrayEquals(mIkeFragPacketOne, resultOk.firstPacket);
+ assertEquals(2, resultOk.ikeMessage.ikePayloadList.size());
+ }
+
+ @Test
+ public void testRcvFirstArrivedFragWithUnprotectedError() throws Exception {
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ null /* collectedFragments*/);
+
+ // Verify that unprotected error was returned
+ IkeException ikeException =
+ verifyDecodeResultErrorAndGetIkeException(
+ decodeResult, DECODE_STATUS_UNPROTECTED_ERROR, mIkeAuthPacket);
+ assertTrue(ikeException instanceof InvalidMessageIdException);
+ }
+
+ @Test
+ public void testRcvLastArrivedFragWithUnprotectedError() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragOne(null /* collectedFragments*/);
+
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID + 1,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvFragWithLargerTotalFragments() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ mIkeFragPacketOne,
+ mDummySkfPayloadOne,
+ PAYLOAD_TYPE_NO_NEXT,
+ null /* collectedFragments*/);
+
+ // Set total fragments of inbound fragment to 5
+ int totalFragments = 5;
+ byte[] fragPacket = makeFragmentBytes(2 /*fragNum*/, totalFragments);
+
+ byte[] unencryptedData = "testRcvFragWithLargerTotalFragments".getBytes();
+ IkeSkfPayload skfPayload =
+ makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_TWO, totalFragments);
+ setDecryptSkfPayload(skfPayload);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ fragPacket,
+ resultPartialIncomplete);
+
+ // Verify that previously collected fragments were all discarded
+ assertTrue(decodeResult instanceof DecodeResultPartial);
+ DecodeResultPartial resultPartial = (DecodeResultPartial) decodeResult;
+
+ assertEquals(PAYLOAD_TYPE_NO_NEXT, resultPartial.firstPayloadType);
+ assertNull(resultPartial.firstFragBytes);
+ assertEquals(mFragTwoHeader, resultPartial.ikeHeader);
+
+ assertEquals(totalFragments, resultPartial.collectedFragsList.length);
+ assertArrayEquals(unencryptedData, resultPartial.collectedFragsList[FRAGMENT_NUM_TWO - 1]);
+ assertFalse(resultPartial.isAllFragmentsReceived());
+ }
+
+ @Test
+ public void testRcvFragWithSmallerTotalFragments() throws Exception {
+ int totalFragments = 5;
+ byte[] unencryptedData = "testRcvFragWithSmallerTotalFragments".getBytes();
+ IkeSkfPayload skfPayload =
+ makeDummySkfPayload(unencryptedData, FRAGMENT_NUM_ONE, totalFragments);
+
+ DecodeResultPartial resultPartialIncomplete =
+ new DecodeResultPartial(
+ mFragOneHeader,
+ mIkeFragPacketOne,
+ skfPayload,
+ PAYLOAD_TYPE_AUTH,
+ null /* collectedFragments*/);
+
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvReplayFrag() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+ setDecryptSkfPayload(mDummySkfPayloadTwo);
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_FRAG_EXPECTED_MESSAGE_ID,
+ mFragTwoHeader,
+ mIkeFragPacketTwo,
+ resultPartialIncomplete);
+
+ // Verify that newly received fragment was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testRcvCompleteMessageDuringReassembly() throws Exception {
+ DecodeResultPartial resultPartialIncomplete =
+ makeDecodeResultForFragTwo(null /* collectedFragments*/);
+
+ DecodeResult decodeResult =
+ decodeSkf(
+ IKE_AUTH_EXPECTED_MESSAGE_ID,
+ mIkeAuthHeader,
+ mIkeAuthPacket,
+ resultPartialIncomplete);
+
+ // Verify that newly received IKE message was discarded
+ assertEquals(resultPartialIncomplete, decodeResult);
+ }
+
+ @Test
+ public void testEncodeAndEncryptFragments() throws Exception {
+ int messageId = 1;
+ int fragSize = 140;
+ int expectedTotalFragments = 3;
+
+ byte[] integrityKey = new byte[0];
+ byte[] encryptionKey = new byte[0];
+ byte[] iv = new byte[IKE_AUTH_CIPHER_IV_SIZE];
+
+ when(mMockCipher.generateIv()).thenReturn(iv);
+ when(mMockCipher.encrypt(any(), any(), any()))
+ .thenAnswer(
+ (invocation) -> {
+ return (byte[]) invocation.getArguments()[0];
+ });
+
+ IkeHeader ikeHeader =
+ new IkeHeader(
+ INIT_SPI,
+ RESP_SPI,
+ IkePayload.PAYLOAD_TYPE_SK,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ false /*fromInit*/,
+ messageId);
+
+ byte[][] packetList =
+ new IkeMessage.IkeMessageHelper()
+ .encryptAndEncode(
+ ikeHeader,
+ IkePayload.PAYLOAD_TYPE_AUTH,
+ mUnencryptedPaddedData,
+ mMockIntegrity,
+ mMockCipher,
+ integrityKey,
+ encryptionKey,
+ true /*supportFragment*/,
+ fragSize);
+
+ assertEquals(expectedTotalFragments, packetList.length);
+
+ IkeHeader expectedIkeHeader =
+ new IkeHeader(
+ INIT_SPI,
+ RESP_SPI,
+ IkePayload.PAYLOAD_TYPE_SKF,
+ IkeHeader.EXCHANGE_TYPE_IKE_AUTH,
+ true /*isResp*/,
+ false /*fromInit*/,
+ messageId);
+ for (int i = 0; i < packetList.length; i++) {
+ byte[] p = packetList[i];
+
+ // Verify fragment length
+ assertNotNull(p);
+ assertTrue(p.length <= fragSize);
+
+ ByteBuffer packetBuffer = ByteBuffer.wrap(p);
+
+ // Verify IKE header
+ byte[] headerBytes = new byte[IkeHeader.IKE_HEADER_LENGTH];
+ packetBuffer.get(headerBytes);
+
+ ByteBuffer expectedHeadBuffer = ByteBuffer.allocate(IkeHeader.IKE_HEADER_LENGTH);
+ expectedIkeHeader.encodeToByteBuffer(
+ expectedHeadBuffer, p.length - IkeHeader.IKE_HEADER_LENGTH);
+
+ assertArrayEquals(expectedHeadBuffer.array(), headerBytes);
+
+ // Verify fragment payload header
+ packetBuffer.get(new byte[IkePayload.GENERIC_HEADER_LENGTH]);
+ assertEquals(i + 1 /*expetced fragNum*/, Short.toUnsignedInt(packetBuffer.getShort()));
+ assertEquals(expectedTotalFragments, Short.toUnsignedInt(packetBuffer.getShort()));
+ }
+
+ verify(mMockCipher, times(expectedTotalFragments + 1))
+ .encrypt(any(), eq(encryptionKey), eq(iv));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java
new file mode 100644
index 00000000..dd92a45e
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNoncePayloadTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import com.android.internal.net.TestUtils;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public final class IkeNoncePayloadTest {
+
+ private static final String NONCE_PAYLOAD_RAW_HEX_STRING =
+ "29000024c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+ private static final String NONCE_DATA_RAW_HEX_STRING =
+ "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c72cb4240eb5c46412";
+
+ @IkePayload.PayloadType
+ private static final int NEXT_PAYLOAD_TYPE = IkePayload.PAYLOAD_TYPE_NOTIFY;
+
+ @Test
+ public void testEncode() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(NONCE_DATA_RAW_HEX_STRING);
+ IkeNoncePayload payload = new IkeNoncePayload(false, inputPacket);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(NEXT_PAYLOAD_TYPE, byteBuffer);
+
+ byte[] expectedNoncePayload =
+ TestUtils.hexStringToByteArray(NONCE_PAYLOAD_RAW_HEX_STRING);
+ assertArrayEquals(expectedNoncePayload, byteBuffer.array());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java
new file mode 100644
index 00000000..884c76eb
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeNotifyPayloadTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD;
+import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.AuthenticationFailedException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidKeException;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.UnrecognizedIkeProtocolException;
+
+import org.junit.Test;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+public final class IkeNotifyPayloadTest {
+ private static final String NOTIFY_NAT_DETECTION_PAYLOAD_HEX_STRING =
+ "2900001c00004004e54f73b7d83f6beb881eab2051d8663f421d10b0";
+ private static final String NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING =
+ "00004004e54f73b7d83f6beb881eab2051d8663f421d10b0";
+ private static final String NAT_DETECTION_DATA_HEX_STRING =
+ "e54f73b7d83f6beb881eab2051d8663f421d10b0";
+
+ private static final String NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING = "030440092ad4c0a2";
+ private static final int REKEY_SPI = 0x2ad4c0a2;
+
+ private static final String IKE_INITIATOR_SPI_HEX_STRING = "5f54bf6d8b48e6e1";
+ private static final String IKE_RESPODNER_SPI_HEX_STRING = "0000000000000000";
+ private static final String IP_ADDR = "10.80.80.13";
+ private static final int PORT = 500;
+
+ private static final int PROTOCOL_ID_OFFSET = 0;
+
+ @Test
+ public void testDecodeNotifyPayloadSpiUnset() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING);
+ byte[] notifyData = TestUtils.hexStringToByteArray(NAT_DETECTION_DATA_HEX_STRING);
+
+ IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
+ assertEquals(IkePayload.PROTOCOL_ID_UNSET, payload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, payload.spiSize);
+ assertEquals(IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP, payload.notifyType);
+ assertEquals(IkePayload.SPI_NOT_INCLUDED, payload.spi);
+ assertArrayEquals(notifyData, payload.notifyData);
+ }
+
+ @Test
+ public void testDecodeNotifyPayloadSpiSet() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING);
+
+ IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, payload.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IPSEC, payload.spiSize);
+ assertEquals(IkeNotifyPayload.NOTIFY_TYPE_REKEY_SA, payload.notifyType);
+ assertEquals(REKEY_SPI, payload.spi);
+ assertArrayEquals(new byte[0], payload.notifyData);
+ }
+
+ @Test
+ public void testDecodeNotifyPayloadThrowException() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING);
+ // Change Protocol ID to ESP
+ inputPacket[PROTOCOL_ID_OFFSET] = (byte) (IkePayload.PROTOCOL_ID_ESP & 0xFF);
+ try {
+ IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
+ fail("Expected InvalidSyntaxException: Protocol ID should not be ESP");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testGenerateNatDetectionData() throws Exception {
+ long initiatorIkeSpi = Long.parseLong(IKE_INITIATOR_SPI_HEX_STRING, 16);
+ long responderIkespi = Long.parseLong(IKE_RESPODNER_SPI_HEX_STRING, 16);
+ InetAddress inetAddress = InetAddress.getByName(IP_ADDR);
+
+ byte[] netDetectionData =
+ IkeNotifyPayload.generateNatDetectionData(
+ initiatorIkeSpi, responderIkespi, inetAddress, PORT);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(NAT_DETECTION_DATA_HEX_STRING);
+ assertArrayEquals(expectedBytes, netDetectionData);
+ }
+
+ @Test
+ public void testBuildIkeErrorNotifyWithData() throws Exception {
+ int payloadType = 1;
+ IkeNotifyPayload notifyPayload =
+ new IkeNotifyPayload(
+ IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD,
+ new byte[] {(byte) payloadType});
+
+ assertArrayEquals(new byte[] {(byte) payloadType}, notifyPayload.notifyData);
+ assertTrue(notifyPayload.isErrorNotify());
+ assertFalse(notifyPayload.isNewChildSaNotify());
+ }
+
+ @Test
+ public void testBuildIkeErrorNotifyWithoutData() throws Exception {
+ IkeNotifyPayload notifyPayload =
+ new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX);
+
+ assertArrayEquals(new byte[0], notifyPayload.notifyData);
+ assertTrue(notifyPayload.isErrorNotify());
+ assertFalse(notifyPayload.isNewChildSaNotify());
+ }
+
+ @Test
+ public void testBuildChildConfigNotify() throws Exception {
+ IkeNotifyPayload notifyPayload =
+ new IkeNotifyPayload(IkeNotifyPayload.NOTIFY_TYPE_USE_TRANSPORT_MODE);
+
+ assertArrayEquals(new byte[0], notifyPayload.notifyData);
+ assertFalse(notifyPayload.isErrorNotify());
+ assertTrue(notifyPayload.isNewChildSaNotify());
+ }
+
+ @Test
+ public void testBuildChildErrorNotify() throws Exception {
+ IkeNotifyPayload notifyPayload =
+ new IkeNotifyPayload(IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE);
+
+ assertArrayEquals(new byte[0], notifyPayload.notifyData);
+ assertTrue(notifyPayload.isErrorNotify());
+ assertTrue(notifyPayload.isNewChildSaNotify());
+ }
+
+ @Test
+ public void testEncodeNotifyPayload() throws Exception {
+ byte[] inputPacket =
+ TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_BODY_HEX_STRING);
+ IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(payload.getPayloadLength());
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NOTIFY, byteBuffer);
+
+ byte[] expectedNoncePayload =
+ TestUtils.hexStringToByteArray(NOTIFY_NAT_DETECTION_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedNoncePayload, byteBuffer.array());
+ }
+
+ @Test
+ public void testValidateAndBuildIkeExceptionWithData() throws Exception {
+ // Invalid Message ID
+ byte[] dhGroup = new byte[] {(byte) 0x00, (byte) 0x0e};
+ int expectedDhGroup = SaProposal.DH_GROUP_2048_BIT_MODP;
+
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_INVALID_KE_PAYLOAD, dhGroup);
+ IkeProtocolException exception = payload.validateAndBuildIkeException();
+
+ assertTrue(exception instanceof InvalidKeException);
+ assertEquals(ERROR_TYPE_INVALID_KE_PAYLOAD, exception.getErrorType());
+ assertArrayEquals(dhGroup, exception.getErrorData());
+ assertEquals(expectedDhGroup, ((InvalidKeException) exception).getDhGroup());
+ }
+
+ @Test
+ public void testValidateAndBuildIkeExceptionWithoutData() throws Exception {
+ // Invalid Syntax
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED);
+ IkeProtocolException exception = payload.validateAndBuildIkeException();
+
+ assertTrue(exception instanceof AuthenticationFailedException);
+ assertEquals(ERROR_TYPE_AUTHENTICATION_FAILED, exception.getErrorType());
+ assertArrayEquals(new byte[0], exception.getErrorData());
+ }
+
+ @Test
+ public void testValidateAndBuildUnrecognizedIkeException() throws Exception {
+ int unrecognizedType = 0;
+ IkeNotifyPayload payload = new IkeNotifyPayload(unrecognizedType);
+ IkeProtocolException exception = payload.validateAndBuildIkeException();
+
+ assertTrue(exception instanceof UnrecognizedIkeProtocolException);
+ assertEquals(unrecognizedType, exception.getErrorType());
+ assertArrayEquals(new byte[0], exception.getErrorData());
+ }
+
+ @Test
+ public void testValidateAndBuildIkeExceptionWithInvalidPayload() throws Exception {
+ // Build a invalid notify payload
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD);
+
+ try {
+ payload.validateAndBuildIkeException();
+ fail("Expected to fail due to invalid error data");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildIkeExceptionWithStatusNotify() throws Exception {
+ // Rekey notification
+ byte[] inputPacket = TestUtils.hexStringToByteArray(NOTIFY_REKEY_PAYLOAD_BODY_HEX_STRING);
+ IkeNotifyPayload payload = new IkeNotifyPayload(false, inputPacket);
+
+ assertFalse(payload.isErrorNotify());
+
+ try {
+ payload.validateAndBuildIkeException();
+ fail("Expected to fail because it is not error notification");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testGetNotifyTypeString() throws Exception {
+ IkeNotifyPayload payload = new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED);
+
+ assertEquals("Notify(Authentication failed)", payload.getTypeString());
+ }
+
+ @Test
+ public void testGetNotifyTypeStringForUnrecoginizedNotify() throws Exception {
+ int unrecognizedType = 0;
+ IkeNotifyPayload payload = new IkeNotifyPayload(unrecognizedType);
+
+ assertEquals("Notify(0)", payload.getTypeString());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java
new file mode 100644
index 00000000..750ff463
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSaPayloadTest.java
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+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 static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.net.ipsec.ike.SaProposal;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.util.Pair;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.exceptions.NoValidProposalChosenException;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Attribute;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.AttributeDecoder;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.ChildProposal;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.DhGroupTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EsnTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IkeProposal;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.KeyLengthAttribute;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.PrfTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Proposal;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.Transform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.TransformDecoder;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedAttribute;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.UnrecognizedTransform;
+import com.android.internal.net.ipsec.ike.testutils.MockIpSecTestUtils;
+import com.android.server.IpSecService;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public final class IkeSaPayloadTest {
+ private static final String OUTBOUND_SA_PAYLOAD_HEADER = "22000030";
+ private static final String OUTBOUND_PROPOSAL_RAW_PACKET =
+ "0000002C010100040300000C0100000C800E0080030000080300000203000008040"
+ + "000020000000802000002";
+ private static final String INBOUND_PROPOSAL_RAW_PACKET =
+ "0000002c010100040300000c0100000c800e0080030000080300000203000008040"
+ + "000020000000802000002";
+ private static final String INBOUND_TWO_PROPOSAL_RAW_PACKET =
+ "020000dc010100190300000c0100000c800e00800300000c0100000c800e00c0030"
+ + "0000c0100000c800e01000300000801000003030000080300000c0300"
+ + "00080300000d030000080300000e03000008030000020300000803000"
+ + "005030000080200000503000008020000060300000802000007030000"
+ + "080200000403000008020000020300000804000013030000080400001"
+ + "40300000804000015030000080400001c030000080400001d03000008"
+ + "0400001e030000080400001f030000080400000f03000008040000100"
+ + "300000804000012000000080400000e000001000201001a0300000c01"
+ + "000014800e00800300000c01000014800e00c00300000c01000014800"
+ + "e01000300000c0100001c800e01000300000c01000013800e00800300"
+ + "000c01000013800e00c00300000c01000013800e01000300000c01000"
+ + "012800e00800300000c01000012800e00c00300000c01000012800e01"
+ + "000300000802000005030000080200000603000008020000070300000"
+ + "802000004030000080200000203000008040000130300000804000014"
+ + "0300000804000015030000080400001c030000080400001d030000080"
+ + "400001e030000080400001f030000080400000f030000080400001003"
+ + "00000804000012000000080400000e";
+ private static final String INBOUND_CHILD_PROPOSAL_RAW_PACKET =
+ "0000002801030403cae7019f0300000c0100000c800e00800300000803000002000" + "0000805000000";
+ private static final String INBOUND_CHILD_TWO_PROPOSAL_RAW_PACKET =
+ "0200002801030403cae7019f0300000c0100000c800e00800300000803000002000"
+ + "00008050000000000001802030401cae7019e0000000c01000012800e"
+ + "0080";
+ private static final String ENCR_TRANSFORM_RAW_PACKET = "0300000c0100000c800e0080";
+ private static final String PRF_TRANSFORM_RAW_PACKET = "0000000802000002";
+ private static final String INTEG_TRANSFORM_RAW_PACKET = "0300000803000002";
+ private static final String DH_GROUP_TRANSFORM_RAW_PACKET = "0300000804000002";
+ private static final String ESN_TRANSFORM_RAW_PACKET = "0000000805000000";
+
+ private static final int TRANSFORM_TYPE_OFFSET = 4;
+ private static final int TRANSFORM_ID_OFFSET = 7;
+
+ private static final String ATTRIBUTE_RAW_PACKET = "800e0080";
+
+ private static final int PROPOSAL_NUMBER = 1;
+
+ private static final int PROPOSAL_NUMBER_OFFSET = 4;
+ private static final int PROTOCOL_ID_OFFSET = 5;
+
+ // Constants for multiple proposals test
+ private static final byte[] PROPOSAL_NUMBER_LIST = {1, 2};
+
+ private static final Inet4Address LOCAL_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.4.4"));
+ private static final Inet4Address REMOTE_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("8.8.8.8"));
+
+ private static final int DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL_ONE = 0x1234;
+ private static final int DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL_TWO = 0x1235;
+ private static final int DUMMY_CHILD_SPI_RESOURCE_ID_REMOTE = 0x2234;
+
+ private static final int CHILD_SPI_LOCAL_ONE = 0x2ad4c0a2;
+ private static final int CHILD_SPI_LOCAL_TWO = 0x2ad4c0a3;
+ private static final int CHILD_SPI_REMOTE = 0xcae70197;
+
+ private AttributeDecoder mMockedAttributeDecoder;
+ private KeyLengthAttribute mAttributeKeyLength128;
+ private List<Attribute> mAttributeListWithKeyLength128;
+
+ private EncryptionTransform mEncrAesCbc128Transform;
+ private EncryptionTransform mEncrAesGcm8Key128Transform;
+ private IntegrityTransform mIntegHmacSha1Transform;
+ private PrfTransform mPrfHmacSha1Transform;
+ private DhGroupTransform mDhGroup1024Transform;
+
+ private Transform[] mValidNegotiatedTransformSet;
+
+ private IkeSaProposal mIkeSaProposalOne;
+ private IkeSaProposal mIkeSaProposalTwo;
+ private IkeSaProposal[] mTwoIkeSaProposalsArray;
+
+ private ChildSaProposal mChildSaProposalOne;
+ private ChildSaProposal mChildSaProposalTwo;
+ private ChildSaProposal[] mTwoChildSaProposalsArray;
+
+ private MockIpSecTestUtils mMockIpSecTestUtils;
+ private IpSecService mMockIpSecService;
+ private IpSecManager mIpSecManager;
+
+ private IpSecSpiResponse mDummyIpSecSpiResponseLocalOne;
+ private IpSecSpiResponse mDummyIpSecSpiResponseLocalTwo;
+ private IpSecSpiResponse mDummyIpSecSpiResponseRemote;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockedAttributeDecoder = mock(AttributeDecoder.class);
+ mAttributeKeyLength128 = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128);
+ mAttributeListWithKeyLength128 = new LinkedList<>();
+ mAttributeListWithKeyLength128.add(mAttributeKeyLength128);
+
+ mEncrAesCbc128Transform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128);
+ mEncrAesGcm8Key128Transform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_128);
+ mIntegHmacSha1Transform =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+ mPrfHmacSha1Transform = new PrfTransform(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+ mDhGroup1024Transform = new DhGroupTransform(SaProposal.DH_GROUP_1024_BIT_MODP);
+
+ mValidNegotiatedTransformSet =
+ new Transform[] {
+ mEncrAesCbc128Transform,
+ mIntegHmacSha1Transform,
+ mPrfHmacSha1Transform,
+ mDhGroup1024Transform
+ };
+
+ mIkeSaProposalOne =
+ new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1)
+ .build();
+
+ mIkeSaProposalTwo =
+ new IkeSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8,
+ SaProposal.KEY_LEN_AES_128)
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12,
+ SaProposal.KEY_LEN_AES_128)
+ .addPseudorandomFunction(SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC)
+ .addDhGroup(SaProposal.DH_GROUP_1024_BIT_MODP)
+ .addDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP)
+ .build();
+ mTwoIkeSaProposalsArray = new IkeSaProposal[] {mIkeSaProposalOne, mIkeSaProposalTwo};
+
+ mChildSaProposalOne =
+ new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128)
+ .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96)
+ .build();
+ mChildSaProposalTwo =
+ new ChildSaProposal.Builder()
+ .addEncryptionAlgorithm(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8,
+ SaProposal.KEY_LEN_AES_128)
+ .build();
+ mTwoChildSaProposalsArray =
+ new ChildSaProposal[] {mChildSaProposalOne, mChildSaProposalTwo};
+
+ mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec();
+ mIpSecManager = mMockIpSecTestUtils.getIpSecManager();
+
+ IpSecService mMockIpSecService = mMockIpSecTestUtils.getIpSecService();
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(LOCAL_ADDRESS.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_LOCAL_ONE))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_LOCAL_TWO));
+
+ when(mMockIpSecService.allocateSecurityParameterIndex(
+ eq(REMOTE_ADDRESS.getHostAddress()), anyInt(), anyObject()))
+ .thenReturn(MockIpSecTestUtils.buildDummyIpSecSpiResponse(CHILD_SPI_REMOTE));
+ }
+
+ // TODO: Add tearDown() to reset Proposal.sTransformDecoder and Transform.sAttributeDecoder.
+
+ @Test
+ public void testDecodeAttribute() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ Pair<Attribute, Integer> pair = Attribute.readFrom(inputBuffer);
+ Attribute attribute = pair.first;
+
+ assertTrue(attribute instanceof KeyLengthAttribute);
+ assertEquals(Attribute.ATTRIBUTE_TYPE_KEY_LENGTH, attribute.type);
+ assertEquals(SaProposal.KEY_LEN_AES_128, ((KeyLengthAttribute) attribute).keyLength);
+ }
+
+ @Test
+ public void testEncodeAttribute() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mAttributeKeyLength128.getAttributeLength());
+ mAttributeKeyLength128.encodeToByteBuffer(byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ATTRIBUTE_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testDecodeEncryptionTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(mAttributeListWithKeyLength128);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof EncryptionTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_ENCR, transform.type);
+ assertEquals(SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, transform.id);
+ assertTrue(transform.isSupported);
+ }
+
+ @Test
+ public void testDecodeEncryptionTransformWithInvalidKeyLength() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ List<Attribute> attributeList = new LinkedList<>();
+ Attribute keyLengAttr = new KeyLengthAttribute(SaProposal.KEY_LEN_AES_128 + 1);
+ attributeList.add(keyLengAttr);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ try {
+ Transform.readFrom(inputBuffer);
+ fail("Expected InvalidSyntaxException for invalid key length.");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncodeEncryptionTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mEncrAesCbc128Transform.getTransformLength());
+ mEncrAesCbc128Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testConstructEncryptionTransformWithUnsupportedId() throws Exception {
+ try {
+ new EncryptionTransform(-1);
+ fail("Expected IllegalArgumentException for unsupported Transform ID");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testConstructEncryptionTransformWithInvalidKeyLength() throws Exception {
+ try {
+ new EncryptionTransform(SaProposal.ENCRYPTION_ALGORITHM_3DES, 129);
+ fail("Expected IllegalArgumentException for invalid key length.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodePrfTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(PRF_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(new LinkedList<Attribute>());
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof PrfTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_PRF, transform.type);
+ assertEquals(SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1, transform.id);
+ assertTrue(transform.isSupported);
+ }
+
+ @Test
+ public void testEncodePrfTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mPrfHmacSha1Transform.getTransformLength());
+ mPrfHmacSha1Transform.encodeToByteBuffer(true, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(PRF_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testConstructPrfTransformWithUnsupportedId() throws Exception {
+ try {
+ new PrfTransform(-1);
+ fail("Expected IllegalArgumentException for unsupported Transform ID");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeIntegrityTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(new LinkedList<Attribute>());
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof IntegrityTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_INTEG, transform.type);
+ assertEquals(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, transform.id);
+ assertTrue(transform.isSupported);
+ }
+
+ @Test
+ public void testDecodeIntegrityTransformWithUnrecognizedAttribute() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(mAttributeListWithKeyLength128);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof IntegrityTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_INTEG, transform.type);
+ assertEquals(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96, transform.id);
+ assertFalse(transform.isSupported);
+ }
+
+ @Test
+ public void testEncodeIntegrityTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mIntegHmacSha1Transform.getTransformLength());
+ mIntegHmacSha1Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(INTEG_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testConstructIntegrityTransformWithUnsupportedId() throws Exception {
+ try {
+ new IntegrityTransform(-1);
+ fail("Expected IllegalArgumentException for unsupported Transform ID");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeDhGroupTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(DH_GROUP_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(new LinkedList<Attribute>());
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof DhGroupTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_DH, transform.type);
+ assertEquals(SaProposal.DH_GROUP_1024_BIT_MODP, transform.id);
+ assertTrue(transform.isSupported);
+ }
+
+ @Test
+ public void testEncodeDhGroupTransform() throws Exception {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mDhGroup1024Transform.getTransformLength());
+ mDhGroup1024Transform.encodeToByteBuffer(false, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(DH_GROUP_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testConstructDhGroupTransformWithUnsupportedId() throws Exception {
+ try {
+ new DhGroupTransform(-1);
+ fail("Expected IllegalArgumentException for unsupported Transform ID");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeEsnTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(new LinkedList<Attribute>());
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof EsnTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type);
+ assertEquals(EsnTransform.ESN_POLICY_NO_EXTENDED, transform.id);
+ assertTrue(transform.isSupported);
+ }
+
+ @Test
+ public void testDecodeEsnTransformWithUnsupportedId() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET);
+ inputPacket[TRANSFORM_ID_OFFSET] = -1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(new LinkedList<Attribute>());
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof EsnTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type);
+ assertFalse(transform.isSupported);
+ }
+
+ @Test
+ public void testDecodeEsnTransformWithUnrecognizedAttribute() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(mAttributeListWithKeyLength128);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+ assertTrue(transform instanceof EsnTransform);
+ assertEquals(Transform.TRANSFORM_TYPE_ESN, transform.type);
+ assertEquals(EsnTransform.ESN_POLICY_NO_EXTENDED, transform.id);
+ assertFalse(transform.isSupported);
+ }
+
+ @Test
+ public void testEncodeEsnTransform() throws Exception {
+ EsnTransform mEsnTransform = new EsnTransform();
+ ByteBuffer byteBuffer = ByteBuffer.allocate(mEsnTransform.getTransformLength());
+ mEsnTransform.encodeToByteBuffer(true, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(ESN_TRANSFORM_RAW_PACKET);
+
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testDecodeUnrecognizedTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ inputPacket[TRANSFORM_TYPE_OFFSET] = 6;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(mAttributeListWithKeyLength128);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+
+ assertTrue(transform instanceof UnrecognizedTransform);
+ }
+
+ @Test
+ public void testDecodeTransformWithRepeatedAttribute() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ List<Attribute> attributeList = new LinkedList<>();
+ attributeList.add(mAttributeKeyLength128);
+ attributeList.add(mAttributeKeyLength128);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ try {
+ Transform.readFrom(inputBuffer);
+ fail("Expected InvalidSyntaxException for repeated Attribute Type Key Length.");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeTransformWithUnrecognizedTransformId() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ inputPacket[TRANSFORM_ID_OFFSET] = 1;
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any()))
+ .thenReturn(mAttributeListWithKeyLength128);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+
+ assertFalse(transform.isSupported);
+ }
+
+ @Test
+ public void testDecodeTransformWithUnrecogniedAttributeType() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(ENCR_TRANSFORM_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+
+ List<Attribute> attributeList = new LinkedList<>();
+ attributeList.add(mAttributeKeyLength128);
+ Attribute attributeUnrecognized = new UnrecognizedAttribute(1, new byte[0]);
+ attributeList.add(attributeUnrecognized);
+
+ when(mMockedAttributeDecoder.decodeAttributes(anyInt(), any())).thenReturn(attributeList);
+ Transform.sAttributeDecoder = mMockedAttributeDecoder;
+
+ Transform transform = Transform.readFrom(inputBuffer);
+
+ assertFalse(transform.isSupported);
+ }
+
+ @Test
+ public void testTransformEquals() throws Exception {
+ EncryptionTransform mEncrAesGcm8Key128TransformOther =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_128);
+
+ assertEquals(mEncrAesGcm8Key128Transform, mEncrAesGcm8Key128TransformOther);
+
+ EncryptionTransform mEncrAesGcm8Key192Transform =
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8, SaProposal.KEY_LEN_AES_192);
+
+ assertNotEquals(mEncrAesGcm8Key128Transform, mEncrAesGcm8Key192Transform);
+
+ IntegrityTransform mIntegHmacSha1TransformOther =
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+
+ assertNotEquals(mEncrAesGcm8Key128Transform, mIntegHmacSha1Transform);
+ assertEquals(mIntegHmacSha1Transform, mIntegHmacSha1TransformOther);
+ }
+
+ private TransformDecoder getDummyTransformDecoder(Transform[] decodedTransforms) {
+ return new TransformDecoder() {
+ @Override
+ public Transform[] decodeTransforms(int count, ByteBuffer inputBuffer)
+ throws IkeProtocolException {
+ for (int i = 0; i < count; i++) {
+ // Read length field and move position
+ inputBuffer.getShort();
+ int length = Short.toUnsignedInt(inputBuffer.getShort());
+ byte[] temp = new byte[length - 4];
+ inputBuffer.get(temp);
+ }
+ return decodedTransforms;
+ }
+ };
+ }
+
+ @Test
+ public void testDecodeSingleProposal() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+ ByteBuffer inputBuffer = ByteBuffer.wrap(inputPacket);
+ Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
+
+ Proposal proposal = Proposal.readFrom(inputBuffer);
+
+ assertEquals(PROPOSAL_NUMBER, proposal.number);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, proposal.spiSize);
+ assertEquals(IkePayload.SPI_NOT_INCLUDED, proposal.spi);
+ assertFalse(proposal.hasUnrecognizedTransform);
+ assertNotNull(proposal.getSaProposal());
+ }
+
+ @Test
+ public void testDecodeSaRequestWithMultipleProposal() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET);
+ Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
+
+ IkeSaPayload payload = new IkeSaPayload(false, false, inputPacket);
+
+ assertEquals(PROPOSAL_NUMBER_LIST.length, payload.proposalList.size());
+ for (int i = 0; i < payload.proposalList.size(); i++) {
+ Proposal proposal = payload.proposalList.get(i);
+ assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(0, proposal.spiSize);
+ }
+ }
+
+ @Test
+ public void testEncodeProposal() throws Exception {
+ // Construct Proposal for IKE INIT exchange.
+ IkeProposal proposal =
+ IkeProposal.createIkeProposal(
+ (byte) PROPOSAL_NUMBER,
+ IkePayload.SPI_LEN_NOT_INCLUDED,
+ mIkeSaProposalOne,
+ LOCAL_ADDRESS);
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(proposal.getProposalLength());
+ proposal.encodeToByteBuffer(true /*is the last*/, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(OUTBOUND_PROPOSAL_RAW_PACKET);
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testDecodeSaResponseWithMultipleProposal() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_TWO_PROPOSAL_RAW_PACKET);
+ Proposal.sTransformDecoder = getDummyTransformDecoder(new Transform[0]);
+
+ try {
+ new IkeSaPayload(false, true, inputPacket);
+ fail("Expected to fail due to more than one proposal in response SA payload.");
+ } catch (InvalidSyntaxException expected) {
+
+ }
+ }
+
+ @Test
+ public void testBuildOutboundIkeRekeySaResponsePayload() throws Exception {
+ IkeSaPayload saPayload =
+ IkeSaPayload.createRekeyIkeSaResponsePayload(
+ (byte) 1, mIkeSaProposalOne, LOCAL_ADDRESS);
+
+ assertTrue(saPayload.isSaResponse);
+ assertEquals(1, saPayload.proposalList.size());
+
+ IkeProposal proposal = (IkeProposal) saPayload.proposalList.get(0);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IKE, proposal.spiSize);
+ assertEquals(mIkeSaProposalOne, proposal.saProposal);
+
+ assertNotNull(proposal.getIkeSpiResource());
+ }
+
+ @Test
+ public void testBuildOutboundInitialIkeSaRequestPayload() throws Exception {
+ IkeSaPayload saPayload = IkeSaPayload.createInitialIkeSaPayload(mTwoIkeSaProposalsArray);
+
+ assertFalse(saPayload.isSaResponse);
+ assertEquals(PROPOSAL_NUMBER_LIST.length, saPayload.proposalList.size());
+
+ for (int i = 0; i < saPayload.proposalList.size(); i++) {
+ IkeProposal proposal = (IkeProposal) saPayload.proposalList.get(i);
+ assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number);
+ assertEquals(IkePayload.PROTOCOL_ID_IKE, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_NOT_INCLUDED, proposal.spiSize);
+ assertEquals(mTwoIkeSaProposalsArray[i], proposal.saProposal);
+
+ // SA Payload for IKE INIT exchange does not include IKE SPIs.
+ assertNull(proposal.getIkeSpiResource());
+ }
+ }
+
+ @Test
+ public void testBuildOutboundChildSaRequest() throws Exception {
+ IkeSaPayload saPayload =
+ IkeSaPayload.createChildSaRequestPayload(
+ mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS);
+
+ assertFalse(saPayload.isSaResponse);
+ assertEquals(PROPOSAL_NUMBER_LIST.length, saPayload.proposalList.size());
+
+ int[] expectedSpis = new int[] {CHILD_SPI_LOCAL_ONE, CHILD_SPI_LOCAL_TWO};
+ for (int i = 0; i < saPayload.proposalList.size(); i++) {
+ ChildProposal proposal = (ChildProposal) saPayload.proposalList.get(i);
+ assertEquals(PROPOSAL_NUMBER_LIST[i], proposal.number);
+ assertEquals(IkePayload.PROTOCOL_ID_ESP, proposal.protocolId);
+ assertEquals(IkePayload.SPI_LEN_IPSEC, proposal.spiSize);
+ assertEquals(mTwoChildSaProposalsArray[i], proposal.saProposal);
+
+ assertEquals(expectedSpis[i], proposal.getChildSpiResource().getSpi());
+ }
+ }
+
+ @Test
+ public void testEncodeIkeSaPayload() throws Exception {
+ IkeSaPayload saPayload =
+ IkeSaPayload.createInitialIkeSaPayload(new IkeSaProposal[] {mIkeSaProposalOne});
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(saPayload.getPayloadLength());
+ saPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_KE, byteBuffer);
+
+ byte[] expectedBytes =
+ TestUtils.hexStringToByteArray(
+ OUTBOUND_SA_PAYLOAD_HEADER + OUTBOUND_PROPOSAL_RAW_PACKET);
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ private void buildAndVerifyIkeSaRespProposal(
+ byte[] saResponseBytes, Transform[] decodedTransforms) throws Exception {
+ // Build response SA payload from decoding bytes.
+ Proposal.sTransformDecoder = getDummyTransformDecoder(decodedTransforms);
+ IkeSaPayload respPayload = new IkeSaPayload(false, true, saResponseBytes);
+
+ // Build request SA payload for IKE INIT exchange from SaProposal.
+ IkeSaPayload reqPayload = IkeSaPayload.createInitialIkeSaPayload(mTwoIkeSaProposalsArray);
+
+ Pair<IkeProposal, IkeProposal> negotiatedProposalPair =
+ IkeSaPayload.getVerifiedNegotiatedIkeProposalPair(
+ reqPayload, respPayload, REMOTE_ADDRESS);
+ IkeProposal reqProposal = negotiatedProposalPair.first;
+ IkeProposal respProposal = negotiatedProposalPair.second;
+
+ assertEquals(respPayload.proposalList.get(0).getSaProposal(), respProposal.getSaProposal());
+
+ // SA Payload for IKE INIT exchange does not include IKE SPIs.
+ assertNull(reqProposal.getIkeSpiResource());
+ assertNull(respProposal.getIkeSpiResource());
+ }
+
+ @Test
+ public void testGetVerifiedNegotiatedIkeProposal() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+
+ buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet);
+ }
+
+ private void verifyChildSaNegotiation(
+ IkeSaPayload reqPayload,
+ IkeSaPayload respPayload,
+ IpSecManager ipSecManager,
+ InetAddress remoteAddress,
+ boolean isLocalInit)
+ throws Exception {
+ // SA negotiation
+ Pair<ChildProposal, ChildProposal> negotiatedProposalPair =
+ IkeSaPayload.getVerifiedNegotiatedChildProposalPair(
+ reqPayload, respPayload, ipSecManager, remoteAddress);
+ ChildProposal reqProposal = negotiatedProposalPair.first;
+ ChildProposal respProposal = negotiatedProposalPair.second;
+
+ // Verify results
+ assertEquals(respPayload.proposalList.get(0).getSaProposal(), respProposal.getSaProposal());
+
+ int initSpi = isLocalInit ? CHILD_SPI_LOCAL_ONE : CHILD_SPI_REMOTE;
+ int respSpi = isLocalInit ? CHILD_SPI_REMOTE : CHILD_SPI_LOCAL_ONE;
+ assertEquals(initSpi, reqProposal.getChildSpiResource().getSpi());
+ assertEquals(respSpi, respProposal.getChildSpiResource().getSpi());
+
+ // Verify SPIs in unselected Proposals have been released.
+ for (Proposal proposal : reqPayload.proposalList) {
+ if (proposal != reqProposal) {
+ assertNull(((ChildProposal) proposal).getChildSpiResource());
+ }
+ }
+ }
+
+ @Test
+ public void testGetVerifiedNegotiatedChildProposalForLocalCreate() throws Exception {
+ // Build local request
+ IkeSaPayload reqPayload =
+ IkeSaPayload.createChildSaRequestPayload(
+ mTwoChildSaProposalsArray, mIpSecManager, LOCAL_ADDRESS);
+
+ // Build remote response
+ Proposal.sTransformDecoder =
+ getDummyTransformDecoder(mChildSaProposalOne.getAllTransforms());
+ IkeSaPayload respPayload =
+ new IkeSaPayload(
+ false /*critical*/,
+ true /*isResp*/,
+ TestUtils.hexStringToByteArray(INBOUND_CHILD_PROPOSAL_RAW_PACKET));
+
+ verifyChildSaNegotiation(
+ reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, true /*isLocalInit*/);
+ }
+
+ @Test
+ public void testGetVerifiedNegotiatedChildProposalForRemoteCreate() throws Exception {
+ Transform[] transformsOne = mChildSaProposalOne.getAllTransforms();
+ Transform[] transformsTwo = mChildSaProposalTwo.getAllTransforms();
+ Transform[] decodedTransforms = new Transform[transformsOne.length + transformsTwo.length];
+ System.arraycopy(transformsOne, 0, decodedTransforms, 0, transformsOne.length);
+ System.arraycopy(
+ transformsTwo, 0, decodedTransforms, transformsOne.length, transformsTwo.length);
+
+ // Build remote request
+ Proposal.sTransformDecoder = getDummyTransformDecoder(decodedTransforms);
+ IkeSaPayload reqPayload =
+ new IkeSaPayload(
+ false /*critical*/,
+ false /*isResp*/,
+ TestUtils.hexStringToByteArray(INBOUND_CHILD_TWO_PROPOSAL_RAW_PACKET));
+
+ // Build local response
+ IkeSaPayload respPayload =
+ IkeSaPayload.createChildSaResponsePayload(
+ (byte) 1, mChildSaProposalOne, mIpSecManager, LOCAL_ADDRESS);
+
+ verifyChildSaNegotiation(
+ reqPayload, respPayload, mIpSecManager, REMOTE_ADDRESS, false /*isLocalInit*/);
+ }
+
+ // Test throwing when negotiated proposal in SA response payload has unrecognized Transform.
+ @Test
+ public void testGetVerifiedNegotiatedProposalWithUnrecogTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+
+ Transform[] negotiatedTransformSet =
+ Arrays.copyOfRange(
+ mValidNegotiatedTransformSet, 0, mValidNegotiatedTransformSet.length);
+ negotiatedTransformSet[0] = new UnrecognizedTransform(-1, 1, new LinkedList<>());
+
+ try {
+ buildAndVerifyIkeSaRespProposal(inputPacket, negotiatedTransformSet);
+ fail("Expected to fail because negotiated proposal has unrecognized Transform.");
+ } catch (NoValidProposalChosenException expected) {
+ }
+ }
+
+ // Test throwing when negotiated proposal has invalid proposal number.
+ @Test
+ public void testGetVerifiedNegotiatedProposalWithInvalidNumber() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+ inputPacket[PROPOSAL_NUMBER_OFFSET] = (byte) 10;
+
+ try {
+ buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet);
+ fail("Expected to fail due to invalid proposal number.");
+ } catch (NoValidProposalChosenException expected) {
+ }
+ }
+
+ // Test throwing when negotiated proposal has mismatched protocol ID.
+ @Test
+ public void testGetVerifiedNegotiatedProposalWithMisMatchedProtocol() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+ inputPacket[PROTOCOL_ID_OFFSET] = IkePayload.PROTOCOL_ID_ESP;
+
+ try {
+ buildAndVerifyIkeSaRespProposal(inputPacket, mValidNegotiatedTransformSet);
+ fail("Expected to fail due to mismatched protocol ID.");
+ } catch (NoValidProposalChosenException expected) {
+ }
+ }
+
+ // Test throwing when negotiated proposal has Transform that was not proposed in request.
+ @Test
+ public void testGetVerifiedNegotiatedProposalWithMismatchedTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+
+ Transform[] negotiatedTransformSet =
+ Arrays.copyOfRange(
+ mValidNegotiatedTransformSet, 0, mValidNegotiatedTransformSet.length);
+ negotiatedTransformSet[0] = mEncrAesGcm8Key128Transform;
+
+ try {
+ buildAndVerifyIkeSaRespProposal(inputPacket, negotiatedTransformSet);
+ fail("Expected to fail due to mismatched Transform.");
+ } catch (NoValidProposalChosenException expected) {
+ }
+ }
+
+ // Test throwing when negotiated proposal is lack of a certain type Transform.
+ @Test
+ public void testGetVerifiedNegotiatedProposalWithoutTransform() throws Exception {
+ byte[] inputPacket = TestUtils.hexStringToByteArray(INBOUND_PROPOSAL_RAW_PACKET);
+
+ try {
+ buildAndVerifyIkeSaRespProposal(inputPacket, new Transform[0]);
+ fail("Expected to fail due to absence of Transform.");
+ } catch (NoValidProposalChosenException expected) {
+ }
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java
new file mode 100644
index 00000000..898db30f
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkPayloadTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public final class IkeSkPayloadTest {
+
+ private static final String IKE_AUTH_INIT_REQUEST_HEX_STRING =
+ "5f54bf6d8b48e6e1909232b3d1edcb5c2e20230800000001000000ec"
+ + "230000d0b9132b7bb9f658dfdc648e5017a6322a030c316c"
+ + "e55f365760d46426ce5cfc78bd1ed9abff63eb9594c1bd58"
+ + "46de333ecd3ea2b705d18293b130395300ba92a351041345"
+ + "0a10525cea51b2753b4e92b081fd78d995659a98f742278f"
+ + "f9b8fd3e21554865c15c79a5134d66b2744966089e416c60"
+ + "a274e44a9a3f084eb02f3bdce1e7de9de8d9a62773ab563b"
+ + "9a69ba1db03c752acb6136452b8a86c41addb4210d68c423"
+ + "efed80e26edca5fa3fe5d0a5ca9375ce332c474b93fb1fa3"
+ + "59eb4e81ae6e0f22abdad69ba8007d50";
+
+ private static final String IKE_AUTH_INIT_REQUEST_DECRYPTED_BODY_HEX_STRING =
+ "2400000c010000000a50500d2700000c010000000a505050"
+ + "2100001c02000000df7c038aefaaa32d3f44b228b52a3327"
+ + "44dfb2c12c00002c00000028010304032ad4c0a20300000c"
+ + "0100000c800e008003000008030000020000000805000000"
+ + "2d00001801000000070000100000ffff00000000ffffffff"
+ + "2900001801000000070000100000ffff00000000ffffffff"
+ + "29000008000040000000000c0000400100000001";
+
+ private static final String ENCR_KEY_FROM_INIT_TO_RESP = "5cbfd33f75796c0188c4a3a546aec4a1";
+ private static final String INTE_KEY_FROM_INIT_TO_RESP =
+ "554fbf5a05b7f511e05a30ce23d874db9ef55e51";
+
+ private static final String ENCR_ALGO_AES_CBC = "AES/CBC/NoPadding";
+
+ private static final int CHECKSUM_LEN = 12;
+
+ private IkeCipher mAesCbcDecryptCipher;
+ private byte[] mAesCbcDecryptionKey;
+
+ private IkeMacIntegrity mHmacSha1IntegrityMac;
+ private byte[] mHmacSha1IntegrityKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mAesCbcDecryptCipher =
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
+ SaProposal.KEY_LEN_AES_128),
+ IkeMessage.getSecurityProvider());
+ mAesCbcDecryptionKey = TestUtils.hexStringToByteArray(ENCR_KEY_FROM_INIT_TO_RESP);
+ mHmacSha1IntegrityMac =
+ IkeMacIntegrity.create(
+ new IntegrityTransform(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96),
+ IkeMessage.getSecurityProvider());
+ mHmacSha1IntegrityKey = TestUtils.hexStringToByteArray(INTE_KEY_FROM_INIT_TO_RESP);
+ }
+
+ @Test
+ public void testEncode() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_AUTH_INIT_REQUEST_HEX_STRING);
+ byte[] payloadBytes =
+ Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length);
+
+ IkeSkPayload payload =
+ IkePayloadFactory.getIkeSkPayload(
+ false /*isSkf*/,
+ message,
+ mHmacSha1IntegrityMac,
+ mAesCbcDecryptCipher,
+ mHmacSha1IntegrityKey,
+ mAesCbcDecryptionKey)
+ .first;
+ int payloadLength = payload.getPayloadLength();
+ ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_ID_INITIATOR, buffer);
+ assertArrayEquals(payloadBytes, buffer.array());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java
new file mode 100644
index 00000000..3374a7dc
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeSkfPayloadTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.net.ipsec.ike.SaProposal;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.crypto.IkeCipher;
+import com.android.internal.net.ipsec.ike.crypto.IkeMacIntegrity;
+import com.android.internal.net.ipsec.ike.crypto.IkeNormalModeCipher;
+import com.android.internal.net.ipsec.ike.exceptions.InvalidSyntaxException;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.EncryptionTransform;
+import com.android.internal.net.ipsec.ike.message.IkeSaPayload.IntegrityTransform;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+public final class IkeSkfPayloadTest {
+ private static final String IKE_FRAG_MSG_HEX_STRING =
+ "bb3d5237aa558779db24aeb6c9ea183e352023200000000100000134"
+ + "0000011800020002c1471fc7714a3b77a2bfd78dd2df4c048dfed913"
+ + "c5de76cb1f4463ef541df2442b43c65308a47b873502268cc1195f99"
+ + "4f6f1a945f56cb342969936af97d79c560c8e0f8bb1a0874ebfb5d0e"
+ + "610b0fcff96d4197c06e7aef07a3a9ae487555bec95c78b87fe6483c"
+ + "be07e3d132f8594c34dba5b5b463b871d0272af6a1ee701fc6b7b70a"
+ + "22a1b8f63eed50ce6b2253ee63fe2cf0289a5eb715e56b389f72b5ba"
+ + "ecfb7340f4abf9253a8c973d281ed62f3516d130fcaaf2c2145b3047"
+ + "f3a243e60beb2fc28bf05183839caf46bfbfc4f28c9a2224e7d49686"
+ + "52a236a403ecb203a1de1e2144a6f5ce28acc2f93989608af17381fc"
+ + "f965cabe1a448274264b22167abfa047dc88e4bfdc5a492847d36d8b";
+
+ private static final String IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING =
+ "dc89d82f4fccff8d3f0c4f4848571e57205f7dbdce954203983d2147"
+ + "3a9e10ba36876b860d33afbdfe6ebf000240e31f2039f4213e882d1f"
+ + "6f0a24887aed0584f4b50a016d989990fd58297757c7b842cd72b57c"
+ + "2f68cba8a5f06d899ce3fcfbd0419402a1d59f1c5b5b23bd0a4ed525"
+ + "27ed6cef9fd238552fcf6e4cd9f794d2b01ba61438fd21714fbc3e3f"
+ + "443a816751e55d46009ae7fb9f52db0977e453a2d28b0453a9393778"
+ + "3a0b625c27d186c052a7169807537d97e731a3543fc10dca605ca86d"
+ + "1496882e1d009a9216d07d0000001801850014120a00000f02000200"
+ + "0100000d010000";
+
+ private static final String IKE_FRAG_MSG_CHECKSUM = "7abfa047dc88e4bfdc5a492847d36d8b";
+ private static final String IKE_FRAG_MSG_PADDING = "05d925c1b3804aee08";
+
+ private static final int FRAGMENT_NUM = 2;
+ private static final int TOTAL_FRAGMENTS = 2;
+
+ private static final int FRAGMENT_NUM_OFFSET =
+ IkeHeader.IKE_HEADER_LENGTH + IkePayload.GENERIC_HEADER_LENGTH;
+ private static final int TOTAL_FRAGMENTS_OFFET = FRAGMENT_NUM_OFFSET + 2;
+
+ private byte[] mDecryptedData;
+
+ private IkeMacIntegrity mSpyHmacSha256IntegrityMac;
+ private IkeNormalModeCipher mSpyAesCbcCipher;
+
+ @Before
+ public void setUp() throws Exception {
+ mDecryptedData = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING);
+
+ // Set up integrity algorithm
+ mSpyHmacSha256IntegrityMac =
+ spy(
+ IkeMacIntegrity.create(
+ new IntegrityTransform(
+ SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128),
+ IkeMessage.getSecurityProvider()));
+ byte[] expectedChecksum = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_CHECKSUM);
+ doReturn(expectedChecksum).when(mSpyHmacSha256IntegrityMac).generateChecksum(any(), any());
+
+ // Set up encryption algorithm
+ mSpyAesCbcCipher =
+ spy(
+ (IkeNormalModeCipher)
+ IkeCipher.create(
+ new EncryptionTransform(
+ SaProposal.ENCRYPTION_ALGORITHM_AES_CBC,
+ SaProposal.KEY_LEN_AES_128),
+ IkeMessage.getSecurityProvider()));
+ byte[] expectedDecryptedPaddedData =
+ TestUtils.hexStringToByteArray(
+ IKE_FRAG_MSG_DECRYPTED_BODY_HEX_STRING + IKE_FRAG_MSG_PADDING);
+ doReturn(expectedDecryptedPaddedData).when(mSpyAesCbcCipher).decrypt(any(), any(), any());
+ }
+
+ private IkeSkfPayload decodeAndDecryptFragMsg(byte[] message) throws Exception {
+ IkeSkfPayload payload =
+ (IkeSkfPayload)
+ IkePayloadFactory.getIkeSkPayload(
+ true /*isSkf*/,
+ message,
+ mSpyHmacSha256IntegrityMac,
+ mSpyAesCbcCipher,
+ new byte[0] /*integrityKey*/,
+ new byte[0] /*decryptionKey*/)
+ .first;
+ return payload;
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+
+ IkeSkfPayload payload = decodeAndDecryptFragMsg(message);
+
+ assertEquals(IkePayload.PAYLOAD_TYPE_SKF, payload.payloadType);
+ assertEquals(FRAGMENT_NUM, payload.fragmentNum);
+ assertEquals(TOTAL_FRAGMENTS, payload.totalFragments);
+ assertArrayEquals(mDecryptedData, payload.getUnencryptedData());
+ }
+
+ @Test
+ public void testDecodeThrowsForZeroFragNum() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+
+ // Set Fragment Number to zero
+ message[FRAGMENT_NUM_OFFSET] = 0;
+ message[FRAGMENT_NUM_OFFSET + 1] = 0;
+
+ try {
+ decodeAndDecryptFragMsg(message);
+ fail("Expected to fail because Fragment Number is zero.");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsForZeroTotalFragments() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+
+ // Set Total Fragments Number to zero
+ message[TOTAL_FRAGMENTS_OFFET] = 0;
+ message[TOTAL_FRAGMENTS_OFFET + 1] = 0;
+
+ try {
+ decodeAndDecryptFragMsg(message);
+ fail("Expected to fail because Total Fragments Number is zero.");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testDecodeThrowsWhenFragNumIsLargerThanTotalFragments() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+
+ // Set Fragment Number to 5
+ message[FRAGMENT_NUM_OFFSET] = 0;
+ message[FRAGMENT_NUM_OFFSET + 1] = 5;
+
+ // Set Total Fragments Number to 2
+ message[TOTAL_FRAGMENTS_OFFET] = 0;
+ message[TOTAL_FRAGMENTS_OFFET + 1] = 2;
+
+ try {
+ decodeAndDecryptFragMsg(message);
+ fail(
+ "Expected to fail because Fragment Number is larger than"
+ + " Total Fragments Number");
+ } catch (InvalidSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testEncode() throws Exception {
+ byte[] message = TestUtils.hexStringToByteArray(IKE_FRAG_MSG_HEX_STRING);
+ IkeSkfPayload payload = decodeAndDecryptFragMsg(message);
+
+ int payloadLength = payload.getPayloadLength();
+ ByteBuffer buffer = ByteBuffer.allocate(payloadLength);
+ payload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_NO_NEXT, buffer);
+
+ byte[] expectedPayloadBytes =
+ Arrays.copyOfRange(message, IkeHeader.IKE_HEADER_LENGTH, message.length);
+ assertArrayEquals(expectedPayloadBytes, buffer.array());
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java
new file mode 100644
index 00000000..05474379
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTestUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static com.android.internal.net.ipsec.ike.message.IkeMessage.DECODE_STATUS_UNPROTECTED_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.util.Pair;
+
+import com.android.internal.net.TestUtils;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResult;
+import com.android.internal.net.ipsec.ike.message.IkeMessage.DecodeResultError;
+
+import java.nio.ByteBuffer;
+
+/**
+ * IkeTestUtils provides utility methods for testing IKE library.
+ *
+ * <p>TODO: Consider moving it under ikev2/
+ */
+public final class IkeTestUtils {
+ public static IkePayload hexStringToIkePayload(
+ @IkePayload.PayloadType int payloadType, boolean isResp, String payloadHexString)
+ throws IkeProtocolException {
+ byte[] payloadBytes = TestUtils.hexStringToByteArray(payloadHexString);
+ // Returned Pair consists of the IkePayload and the following IkePayload's type.
+ Pair<IkePayload, Integer> pair =
+ IkePayloadFactory.getIkePayload(payloadType, isResp, ByteBuffer.wrap(payloadBytes));
+ return pair.first;
+ }
+
+ public static <T extends IkeProtocolException> T decodeAndVerifyUnprotectedErrorMsg(
+ byte[] inputPacket, Class<T> expectedException) throws Exception {
+ IkeHeader header = new IkeHeader(inputPacket);
+ DecodeResult decodeResult = IkeMessage.decode(0, header, inputPacket);
+
+ assertEquals(DECODE_STATUS_UNPROTECTED_ERROR, decodeResult.status);
+ DecodeResultError resultError = (DecodeResultError) decodeResult;
+ assertNotNull(resultError.ikeException);
+ assertTrue(expectedException.isInstance(resultError.ikeException));
+
+ return (T) resultError.ikeException;
+ }
+
+ public static IkeSkfPayload makeDummySkfPayload(
+ byte[] unencryptedData, int fragNum, int totalFrags) throws Exception {
+ IkeEncryptedPayloadBody mockEncryptedBody = mock(IkeEncryptedPayloadBody.class);
+ when(mockEncryptedBody.getUnencryptedData()).thenReturn(unencryptedData);
+ return new IkeSkfPayload(mockEncryptedBody, fragNum, totalFrags);
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java
new file mode 100644
index 00000000..cbd6f930
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/message/IkeTsPayloadTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 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.message;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import com.android.internal.net.TestUtils;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Test;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+public final class IkeTsPayloadTest {
+ private static final String TS_INITIATOR_PAYLOAD_HEX_STRING =
+ "2d00002802000000070000100010fff0c0000264c0000365070000100000ffffc0000464c0000466";
+
+ private static final int NUMBER_OF_TS = 2;
+
+ private static final int TS_ONE_START_PORT = 16;
+ private static final int TS_ONE_END_PORT = 65520;
+ private static final Inet4Address TS_ONE_START_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.100"));
+ private static final Inet4Address TS_ONE_END_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.3.101"));
+
+ private static final int TS_TWO_START_PORT = 0;
+ private static final int TS_TWO_END_PORT = 65535;
+ private static final Inet4Address TS_TWO_START_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.100"));
+ private static final Inet4Address TS_TWO_END_ADDRESS =
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.4.102"));
+
+ private IkeTrafficSelector mTsOne;
+ private IkeTrafficSelector mTsTwo;
+
+ public IkeTsPayloadTest() {
+ mTsOne =
+ new IkeTrafficSelector(
+ IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
+ TS_ONE_START_PORT,
+ TS_ONE_END_PORT,
+ TS_ONE_START_ADDRESS,
+ TS_ONE_END_ADDRESS);
+ mTsTwo =
+ new IkeTrafficSelector(
+ IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
+ TS_TWO_START_PORT,
+ TS_TWO_END_PORT,
+ TS_TWO_START_ADDRESS,
+ TS_TWO_END_ADDRESS);
+ }
+
+ @Test
+ public void testDecodeTsInitiatorPayload() throws Exception {
+ ByteBuffer inputBuffer =
+ ByteBuffer.wrap(TestUtils.hexStringToByteArray(TS_INITIATOR_PAYLOAD_HEX_STRING));
+
+ IkePayload payload =
+ IkePayloadFactory.getIkePayload(
+ IkePayload.PAYLOAD_TYPE_TS_INITIATOR, false, inputBuffer)
+ .first;
+ assertTrue(payload instanceof IkeTsPayload);
+
+ IkeTsPayload tsPayload = (IkeTsPayload) payload;
+ assertEquals(IkePayload.PAYLOAD_TYPE_TS_INITIATOR, tsPayload.payloadType);
+ assertEquals(NUMBER_OF_TS, tsPayload.numTs);
+ }
+
+ @Test
+ public void testBuildAndEncodeTsPayload() throws Exception {
+ IkeTsPayload tsPayload =
+ new IkeTsPayload(true /*isInitiator*/, new IkeTrafficSelector[] {mTsOne, mTsTwo});
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(tsPayload.getPayloadLength());
+ tsPayload.encodeToByteBuffer(IkePayload.PAYLOAD_TYPE_TS_RESPONDER, byteBuffer);
+
+ byte[] expectedBytes = TestUtils.hexStringToByteArray(TS_INITIATOR_PAYLOAD_HEX_STRING);
+ assertArrayEquals(expectedBytes, byteBuffer.array());
+ }
+
+ @Test
+ public void testContains() throws Exception {
+ IkeTrafficSelector tsOneNarrowPortRange =
+ new IkeTrafficSelector(
+ IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
+ TS_ONE_START_PORT + 1,
+ TS_ONE_END_PORT,
+ TS_ONE_START_ADDRESS,
+ TS_ONE_END_ADDRESS);
+
+ IkeTrafficSelector tsOneNarrowAddressRange =
+ new IkeTrafficSelector(
+ IkeTrafficSelector.TRAFFIC_SELECTOR_TYPE_IPV4_ADDR_RANGE,
+ TS_ONE_START_PORT,
+ TS_ONE_END_PORT,
+ (Inet4Address) (InetAddressUtils.parseNumericAddress("192.0.2.200")),
+ TS_ONE_END_ADDRESS);
+
+ IkeTsPayload tsPayload =
+ new IkeTsPayload(true /*isInitiator*/, new IkeTrafficSelector[] {mTsOne, mTsTwo});
+
+ IkeTsPayload tsNarrowPortRangePayload =
+ new IkeTsPayload(
+ true /*isInitiator*/,
+ new IkeTrafficSelector[] {tsOneNarrowPortRange, mTsTwo});
+
+ IkeTsPayload tsNarrowAddressRangePayload =
+ new IkeTsPayload(
+ true /*isInitiator*/,
+ new IkeTrafficSelector[] {tsOneNarrowAddressRange, mTsTwo});
+
+ assertTrue(tsPayload.contains(tsPayload));
+ assertTrue(tsPayload.contains(tsNarrowPortRangePayload));
+ assertTrue(tsPayload.contains(tsNarrowAddressRangePayload));
+
+ assertFalse(tsNarrowPortRangePayload.contains(tsPayload));
+ assertFalse(tsNarrowAddressRangePayload.contains(tsPayload));
+ assertFalse(tsNarrowAddressRangePayload.contains(tsNarrowPortRangePayload));
+ assertFalse(tsNarrowPortRangePayload.contains(tsNarrowAddressRangePayload));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java
new file mode 100644
index 00000000..db128c80
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/CertUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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.testutils;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+import com.android.org.bouncycastle.util.io.pem.PemObject;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.security.KeyFactory;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/** CertUtils provides utility methods for creating X509 certificate and private key. */
+public final class CertUtils {
+ private static final String PEM_FOLDER_NAME = "pem";
+ private static final String KEY_FOLDER_NAME = "key";
+
+ /** Creates an X509Certificate with a pem file */
+ public static X509Certificate createCertFromPemFile(String fileName) throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ InputStream inputStream =
+ context.getResources().getAssets().open(PEM_FOLDER_NAME + "/" + fileName);
+
+ CertificateFactory factory =
+ CertificateFactory.getInstance("X.509", IkeMessage.getSecurityProvider());
+ return (X509Certificate) factory.generateCertificate(inputStream);
+ }
+
+ /** Creates an private key from a PKCS8 format key file */
+ public static RSAPrivateKey createRsaPrivateKeyFromKeyFile(String fileName) throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ InputStream inputStream =
+ context.getResources().getAssets().open(KEY_FOLDER_NAME + "/" + fileName);
+
+ PemObject pemObject = new PemReader(new InputStreamReader(inputStream)).readPemObject();
+
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return (RSAPrivateKey)
+ keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pemObject.getContent()));
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java
new file mode 100644
index 00000000..742ff240
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/testutils/MockIpSecTestUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.testutils;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecUdpEncapResponse;
+import android.system.Os;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.IpSecService;
+
+/** This class provides utility methods for mocking IPsec surface. */
+public final class MockIpSecTestUtils {
+ private static final int DUMMY_CHILD_SPI = 0x2ad4c0a2;
+ private static final int DUMMY_CHILD_SPI_RESOURCE_ID = 0x1234;
+
+ private static final int DUMMY_UDP_ENCAP_PORT = 34567;
+ private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234;
+
+ private Context mContext;
+ private IpSecService mMockIpSecService;
+ private IpSecManager mIpSecManager;
+
+ private MockIpSecTestUtils() throws Exception {
+ mMockIpSecService = mock(IpSecService.class);
+ mContext = InstrumentationRegistry.getContext();
+ mIpSecManager = new IpSecManager(mContext, mMockIpSecService);
+
+ when(mMockIpSecService.allocateSecurityParameterIndex(anyString(), anyInt(), anyObject()))
+ .thenReturn(
+ new IpSecSpiResponse(
+ IpSecManager.Status.OK,
+ DUMMY_CHILD_SPI_RESOURCE_ID,
+ DUMMY_CHILD_SPI));
+
+ when(mMockIpSecService.openUdpEncapsulationSocket(anyInt(), anyObject()))
+ .thenReturn(
+ new IpSecUdpEncapResponse(
+ IpSecManager.Status.OK,
+ DUMMY_UDP_ENCAP_RESOURCE_ID,
+ DUMMY_UDP_ENCAP_PORT,
+ Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)));
+ }
+
+ public static MockIpSecTestUtils setUpMockIpSec() throws Exception {
+ return new MockIpSecTestUtils();
+ }
+
+ public static IpSecSpiResponse buildDummyIpSecSpiResponse(int spi) throws Exception {
+ return new IpSecSpiResponse(IpSecManager.Status.OK, DUMMY_CHILD_SPI_RESOURCE_ID, spi);
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public IpSecManager getIpSecManager() {
+ return mIpSecManager;
+ }
+
+ public IpSecService getIpSecService() {
+ return mMockIpSecService;
+ }
+}
diff --git a/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java
new file mode 100644
index 00000000..1fddb1d9
--- /dev/null
+++ b/tests/iketests/src/java/com/android/internal/net/ipsec/ike/utils/RetransmitterTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 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.utils;
+
+import static com.android.internal.net.ipsec.ike.IkeSessionStateMachine.CMD_RETRANSMIT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.net.ipsec.ike.message.IkeMessage;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class RetransmitterTest {
+ private Handler mMockHandler;
+ private IkeMessage mMockIkeMessage;
+ private TestRetransmitter mRetransmitter;
+
+ private class TestRetransmitter extends Retransmitter {
+ int mSendCallCount; // Defaults to 0
+ boolean mFailed; // Defaults to false
+
+ TestRetransmitter(Handler handler, IkeMessage message) {
+ super(handler, message);
+ }
+
+ @Override
+ public void send(IkeMessage msg) {
+ mSendCallCount++;
+ }
+
+ @Override
+ public void handleRetransmissionFailure() {
+ mFailed = true;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMockHandler = mock(Handler.class);
+ when(mMockHandler.obtainMessage(eq(CMD_RETRANSMIT), anyObject()))
+ .thenReturn(mock(Message.class));
+
+ mMockIkeMessage = mock(IkeMessage.class);
+ mRetransmitter = new TestRetransmitter(mMockHandler, mMockIkeMessage);
+ }
+
+ @Test
+ public void testSendRequestAndQueueRetransmit() throws Exception {
+ mRetransmitter.retransmit();
+ assertEquals(1, mRetransmitter.mSendCallCount);
+ verify(mMockHandler).obtainMessage(eq(CMD_RETRANSMIT), eq(mRetransmitter));
+ verify(mMockHandler)
+ .sendMessageDelayed(any(Message.class), eq(Retransmitter.RETRANSMIT_TIMEOUT_MS));
+ }
+
+ @Test
+ public void testRetransmitQueuesExponentialRetransmit() throws Exception {
+ mRetransmitter.retransmit();
+
+ for (int i = 0; i <= Retransmitter.RETRANSMIT_MAX_ATTEMPTS; i++) {
+ long expectedTimeout =
+ (long)
+ (Retransmitter.RETRANSMIT_TIMEOUT_MS
+ * Math.pow(Retransmitter.RETRANSMIT_BACKOFF_FACTOR, i));
+
+ assertEquals(i + 1, mRetransmitter.mSendCallCount);
+ assertFalse(mRetransmitter.mFailed);
+
+ // This call happens with the same arguments each time
+ verify(mMockHandler, times(i + 1))
+ .obtainMessage(eq(CMD_RETRANSMIT), eq(mRetransmitter));
+
+ // But the expected timeout changes every time
+ verify(mMockHandler).sendMessageDelayed(any(Message.class), eq(expectedTimeout));
+
+ verifyNoMoreInteractions(mMockHandler);
+
+ // Trigger next round of retransmissions.
+ mRetransmitter.retransmit();
+ }
+ }
+
+ @Test
+ public void testRetransmitterCallsRetranmissionsFailedOnMaxTries() throws Exception {
+ mRetransmitter.retransmit();
+
+ // Exhaust all retransmit attempts
+ for (int i = 0; i <= Retransmitter.RETRANSMIT_MAX_ATTEMPTS; i++) {
+ mRetransmitter.retransmit();
+ }
+
+ assertTrue(mRetransmitter.mFailed);
+ }
+
+ @Test
+ public void testRetransmitterStopsRetransmitting() throws Exception {
+ mRetransmitter.stopRetransmitting();
+
+ verify(mMockHandler).removeMessages(eq(CMD_RETRANSMIT), eq(mRetransmitter));
+ }
+}