diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:19:18 +0000 |
commit | db74d7937f3a9503f0e2590ec24c3bcfbf259ce0 (patch) | |
tree | b39c3e794945072d38b97c963c5a6f10753514e1 /tests/iketests/src/java/com/android/internal/net/ipsec | |
parent | 7ffef5cae6d3c93c93b4055fd22517d44f77acda (diff) | |
parent | eb4c77d7228f956f928c7d3500a220339ee78388 (diff) | |
download | ike-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')
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)); + } +} |