| /* |
| * 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.ike.ikev2; |
| |
| import static com.android.ike.ikev2.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD; |
| import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX; |
| import static com.android.ike.ikev2.message.IkeMessage.DECODE_STATUS_OK; |
| import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP; |
| import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP; |
| import static com.android.ike.ikev2.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.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| 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.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.os.Handler; |
| import android.os.Looper; |
| import android.os.test.TestLooper; |
| |
| import com.android.ike.TestUtils; |
| import com.android.ike.ikev2.ChildSessionStateMachine.IChildSessionSmCallback; |
| import com.android.ike.ikev2.ChildSessionStateMachineFactory.ChildSessionFactoryHelper; |
| import com.android.ike.ikev2.ChildSessionStateMachineFactory.IChildSessionFactoryHelper; |
| import com.android.ike.ikev2.IkeIdentification.IkeIpv4AddrIdentification; |
| import com.android.ike.ikev2.IkeLocalRequestScheduler.ChildLocalRequest; |
| import com.android.ike.ikev2.IkeLocalRequestScheduler.LocalRequest; |
| import com.android.ike.ikev2.IkeSessionStateMachine.IkeSecurityParameterIndex; |
| import com.android.ike.ikev2.IkeSessionStateMachine.ReceivedIkePacket; |
| import com.android.ike.ikev2.SaRecord.ISaRecordHelper; |
| import com.android.ike.ikev2.SaRecord.IkeSaRecord; |
| import com.android.ike.ikev2.SaRecord.IkeSaRecordConfig; |
| import com.android.ike.ikev2.SaRecord.SaRecordHelper; |
| import com.android.ike.ikev2.crypto.IkeCipher; |
| import com.android.ike.ikev2.crypto.IkeMacIntegrity; |
| import com.android.ike.ikev2.crypto.IkeMacPrf; |
| import com.android.ike.ikev2.exceptions.IkeProtocolException; |
| import com.android.ike.ikev2.message.IkeAuthPskPayload; |
| import com.android.ike.ikev2.message.IkeDeletePayload; |
| import com.android.ike.ikev2.message.IkeHeader; |
| import com.android.ike.ikev2.message.IkeIdPayload; |
| import com.android.ike.ikev2.message.IkeInformationalPayload; |
| import com.android.ike.ikev2.message.IkeKePayload; |
| import com.android.ike.ikev2.message.IkeMessage; |
| import com.android.ike.ikev2.message.IkeMessage.DecodeResult; |
| import com.android.ike.ikev2.message.IkeMessage.IIkeMessageHelper; |
| import com.android.ike.ikev2.message.IkeMessage.IkeMessageHelper; |
| import com.android.ike.ikev2.message.IkeNoncePayload; |
| import com.android.ike.ikev2.message.IkeNotifyPayload; |
| import com.android.ike.ikev2.message.IkePayload; |
| import com.android.ike.ikev2.message.IkeSaPayload; |
| import com.android.ike.ikev2.message.IkeSaPayload.DhGroupTransform; |
| import com.android.ike.ikev2.message.IkeSaPayload.EncryptionTransform; |
| import com.android.ike.ikev2.message.IkeSaPayload.EsnTransform; |
| import com.android.ike.ikev2.message.IkeSaPayload.IntegrityTransform; |
| import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform; |
| import com.android.ike.ikev2.message.IkeTestUtils; |
| import com.android.ike.ikev2.message.IkeTsPayload; |
| |
| import libcore.net.InetAddressUtils; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.ArgumentCaptor; |
| |
| import java.io.IOException; |
| import java.net.Inet4Address; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| public final class 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 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 DELETE_IKE_PAYLOAD_HEX_STRING = "0000000801000000"; |
| private static final String NOTIFY_REKEY_IKE_PAYLOAD_HEX_STRING = "2100000800004009"; |
| 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 CHILD_SA_RESP_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 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 DUMMY_CHILD_SPI_RESOURCE_ID_LOCAL = 0x1234; |
| private static final int CHILD_SPI_LOCAL = 0x2ad4c0a2; |
| |
| private static final int DUMMY_UDP_ENCAP_RESOURCE_ID = 0x3234; |
| private static final int UDP_ENCAP_PORT = 34567; |
| |
| private static long sIkeInitResponseSpiBase = 1L; |
| |
| private MockIpSecTestUtils mMockIpSecTestUtils; |
| private Context mContext; |
| private IpSecManager mIpSecManager; |
| private UdpEncapsulationSocket mUdpEncapSocket; |
| |
| private TestLooper mLooper; |
| private IkeSessionStateMachine mIkeSessionStateMachine; |
| |
| private IkeSessionOptions mIkeSessionOptions; |
| private ChildSessionOptions mChildSessionOptions; |
| |
| private Handler mUserCbHandler; |
| private IIkeSessionCallback mMockIkeSessionCallback; |
| private IChildSessionCallback mMockChildSessionCallback; |
| |
| private EncryptionTransform mIkeEncryptionTransform; |
| private IntegrityTransform mIkeIntegrityTransform; |
| private PrfTransform mIkePrfTransform; |
| private DhGroupTransform mIkeDhGroupTransform; |
| |
| private IIkeMessageHelper mMockIkeMessageHelper; |
| private ISaRecordHelper mMockSaRecordHelper; |
| |
| private ChildSessionStateMachine mMockChildSessionStateMachine; |
| private IChildSessionFactoryHelper mMockChildSessionFactoryHelper; |
| |
| private IkeSaRecord mSpyCurrentIkeSaRecord; |
| private IkeSaRecord mSpyLocalInitIkeSaRecord; |
| private IkeSaRecord mSpyRemoteInitIkeSaRecord; |
| |
| private int mExpectedCurrentSaLocalReqMsgId; |
| private int mExpectedCurrentSaRemoteReqMsgId; |
| |
| 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); |
| |
| 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 DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null)); |
| |
| 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 { |
| boolean fromIkeInit = !ikeSaRecord.isLocalInit; |
| IkeMessage dummyIkeMessage = |
| makeDummyIkeMessageForTest( |
| ikeSaRecord.getInitiatorSpi(), |
| ikeSaRecord.getResponderSpi(), |
| eType, |
| isResp, |
| fromIkeInit, |
| isResp |
| ? ikeSaRecord.getLocalRequestMessageId() |
| : ikeSaRecord.getRemoteRequestMessageId(), |
| true /*isEncyprted*/, |
| payloadList); |
| |
| byte[] dummyIkePacketBytes = new byte[0]; |
| when(mMockIkeMessageHelper.decode( |
| anyInt(), |
| any(), |
| any(), |
| eq(ikeSaRecord), |
| eq(dummyIkeMessage.ikeHeader), |
| eq(dummyIkePacketBytes))) |
| .thenReturn(new DecodeResult(DECODE_STATUS_OK, dummyIkeMessage, null)); |
| |
| return new ReceivedIkePacket(dummyIkeMessage.ikeHeader, dummyIkePacketBytes); |
| } |
| |
| 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)); |
| } |
| |
| public IkeSessionStateMachineTest() throws Exception { |
| mMockIkeMessageHelper = mock(IkeMessage.IIkeMessageHelper.class); |
| mMockSaRecordHelper = mock(SaRecord.ISaRecordHelper.class); |
| |
| mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class); |
| mMockChildSessionFactoryHelper = mock(IChildSessionFactoryHelper.class); |
| |
| when(mMockIkeMessageHelper.encode(any())).thenReturn(new byte[0]); |
| when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any())) |
| .thenReturn(new byte[0]); |
| } |
| |
| 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)); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| if (Looper.myLooper() == null) Looper.prepare(); |
| |
| mMockIpSecTestUtils = MockIpSecTestUtils.setUpMockIpSec(); |
| mIpSecManager = mMockIpSecTestUtils.getIpSecManager(); |
| mContext = mMockIpSecTestUtils.getContext(); |
| mUdpEncapSocket = mIpSecManager.openUdpEncapsulationSocket(); |
| |
| mIkeSessionOptions = buildIkeSessionOptions(); |
| 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); |
| |
| mUserCbHandler = new Handler(); |
| mMockIkeSessionCallback = mock(IIkeSessionCallback.class); |
| mMockChildSessionCallback = mock(IChildSessionCallback.class); |
| |
| mLooper = new TestLooper(); |
| |
| ChildSessionStateMachineFactory.setChildSessionFactoryHelper( |
| mMockChildSessionFactoryHelper); |
| setupChildStateMachineFactory(mMockChildSessionStateMachine); |
| |
| // Setup state machine |
| mIkeSessionStateMachine = |
| new IkeSessionStateMachine( |
| mLooper.getLooper(), |
| mContext, |
| mIpSecManager, |
| mIkeSessionOptions, |
| mChildSessionOptions, |
| mUserCbHandler, |
| mMockIkeSessionCallback, |
| mMockChildSessionCallback); |
| mIkeSessionStateMachine.setDbg(true); |
| mIkeSessionStateMachine.start(); |
| |
| mLooper.dispatchAll(); |
| mIkeSessionStateMachine.mLocalAddress = LOCAL_ADDRESS; |
| |
| IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper); |
| 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(); |
| |
| IkeMessage.setIkeMessageHelper(new IkeMessageHelper()); |
| SaRecord.setSaRecordHelper(new SaRecordHelper()); |
| ChildSessionStateMachineFactory.setChildSessionFactoryHelper( |
| new ChildSessionFactoryHelper()); |
| } |
| |
| private SaProposal buildSaProposal() throws Exception { |
| return SaProposal.Builder.newIkeSaProposalBuilder() |
| .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 buildIkeSessionOptions() throws Exception { |
| SaProposal saProposal = buildSaProposal(); |
| |
| byte[] psk = TestUtils.hexStringToByteArray(PSK_HEX_STRING); |
| |
| IkeSessionOptions sessionOptions = |
| new IkeSessionOptions.Builder(REMOTE_ADDRESS, mUdpEncapSocket) |
| .addSaProposal(saProposal) |
| .setLocalIdentification( |
| new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS)) |
| .setRemoteIdentification( |
| new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS)) |
| .setLocalAuthPsk(psk) |
| .setRemoteAuthPsk(psk) |
| .build(); |
| return sessionOptions; |
| } |
| |
| private ChildSessionOptions buildChildSessionOptions() throws Exception { |
| SaProposal saProposal = |
| SaProposal.Builder.newChildSaProposalBuilder() |
| .addEncryptionAlgorithm( |
| SaProposal.ENCRYPTION_ALGORITHM_AES_CBC, SaProposal.KEY_LEN_AES_128) |
| .addIntegrityAlgorithm(SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96) |
| .build(); |
| |
| return new ChildSessionOptions.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); |
| |
| 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); |
| |
| // 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 /*initiatorSpi*/, |
| ++sIkeInitResponseSpiBase, |
| IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT, |
| true /*isResp*/, |
| false /*fromIkeInit*/, |
| payloadTypeList, |
| payloadHexStringList); |
| } |
| |
| private ReceivedIkePacket makeIkeAuthResponse() throws Exception { |
| List<Integer> payloadTypeList = new LinkedList<>(); |
| List<String> payloadHexStringList = new LinkedList<>(); |
| |
| payloadTypeList.add(IkePayload.PAYLOAD_TYPE_ID_RESPONDER); |
| payloadTypeList.add(IkePayload.PAYLOAD_TYPE_AUTH); |
| payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA); |
| payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_INITIATOR); |
| payloadTypeList.add(IkePayload.PAYLOAD_TYPE_TS_RESPONDER); |
| |
| payloadHexStringList.add(ID_PAYLOAD_RESPONDER_HEX_STRING); |
| payloadHexStringList.add(PSK_AUTH_RESP_PAYLOAD_HEX_STRING); |
| payloadHexStringList.add(CHILD_SA_RESP_PAYLOAD_HEX_STRING); |
| payloadHexStringList.add(TS_INIT_PAYLOAD_HEX_STRING); |
| payloadHexStringList.add(TS_RESP_PAYLOAD_HEX_STRING); |
| |
| return makeDummyEncryptedReceivedIkePacket( |
| mSpyCurrentIkeSaRecord, |
| IkeHeader.EXCHANGE_TYPE_IKE_AUTH, |
| true /*isResp*/, |
| payloadTypeList, |
| payloadHexStringList); |
| } |
| |
| private ReceivedIkePacket makeCreateChildResponse() 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_RESP_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 makeDummyEncryptedReceivedIkePacket( |
| mSpyCurrentIkeSaRecord, |
| IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, |
| true /*isResp*/, |
| payloadTypeList, |
| payloadHexStringList); |
| } |
| |
| 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_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 makeRekeyIkeRequest() 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_INIT_PAYLOAD_HEX_STRING); |
| return makeDummyEncryptedReceivedIkePacket( |
| mSpyCurrentIkeSaRecord, |
| IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, |
| false /*isResp*/, |
| payloadTypeList, |
| payloadHexStringList); |
| } |
| |
| 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 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()); |
| } |
| |
| @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(); |
| } |
| |
| @Test |
| public void testCreateIkeLocalIkeInit() throws Exception { |
| when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any())) |
| .thenReturn(mSpyCurrentIkeSaRecord); |
| |
| // 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); |
| 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)); |
| |
| IkeSocket ikeSocket = mIkeSessionStateMachine.mIkeSocket; |
| assertNotNull(ikeSocket); |
| assertNotEquals( |
| -1 /*not found*/, ikeSocket.mSpiToIkeSession.indexOfValue(mIkeSessionStateMachine)); |
| |
| verify(mMockIkeMessageHelper) |
| .decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() |
| instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth); |
| |
| // Validate negotiated SA proposal. |
| SaProposal negotiatedProposal = mIkeSessionStateMachine.mSaProposal; |
| assertNotNull(negotiatedProposal); |
| |
| assertEquals( |
| new EncryptionTransform[] {mIkeEncryptionTransform}, |
| negotiatedProposal.getEncryptionTransforms()); |
| assertEquals( |
| new IntegrityTransform[] {mIkeIntegrityTransform}, |
| negotiatedProposal.getIntegrityTransforms()); |
| assertEquals(new PrfTransform[] {mIkePrfTransform}, negotiatedProposal.getPrfTransforms()); |
| assertEquals(new EsnTransform[0], negotiatedProposal.getEsnTransforms()); |
| |
| // 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); |
| |
| // Validate NAT detection |
| assertTrue(mIkeSessionStateMachine.mIsLocalBehindNat); |
| assertFalse(mIkeSessionStateMachine.mIsRemoteBehindNat); |
| } |
| |
| /** Initializes the mIkeSessionStateMachine in the IDLE state. */ |
| private void setupIdleStateMachine() 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.addIkeSaRecord(mSpyCurrentIkeSaRecord); |
| |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle); |
| mLooper.dispatchAll(); |
| |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| } |
| |
| private void mockIkeSetup() throws Exception { |
| when(mMockSaRecordHelper.makeFirstIkeSaRecord(any(), any(), any())) |
| .thenReturn(mSpyCurrentIkeSaRecord); |
| |
| // Send IKE INIT request |
| mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE); |
| |
| // Receive IKE INIT response |
| ReceivedIkePacket dummyIkeInitRespReceivedPacket = makeIkeInitResponse(); |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyIkeInitRespReceivedPacket); |
| mLooper.dispatchAll(); |
| verifyIncrementLocaReqMsgId(); |
| |
| IkeMessage mockIkeInitResp = mock(IkeMessage.class); |
| mIkeSessionStateMachine.mIkeInitResponseBytes = |
| TestUtils.hexStringToByteArray(IKE_INIT_RESP_HEX_STRING); |
| mIkeSessionStateMachine.mIkeRespNoncePayload = |
| (IkeNoncePayload) |
| IkeTestUtils.hexStringToIkePayload( |
| IkePayload.PAYLOAD_TYPE_NONCE, |
| true /*isResp*/, |
| NONCE_RESP_PAYLOAD_HEX_STRING); |
| |
| // Receive IKE AUTH response |
| ReceivedIkePacket dummyIkeAuthRespReceivedPacket = makeIkeAuthResponse(); |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyIkeAuthRespReceivedPacket); |
| mLooper.dispatchAll(); |
| |
| verifyIncrementLocaReqMsgId(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() |
| instanceof IkeSessionStateMachine.ChildProcedureOngoing); |
| |
| // Mock finishing first Child SA negotiation. |
| verify(mMockChildSessionFactoryHelper) |
| .makeChildSessionStateMachine( |
| eq(mLooper.getLooper()), |
| eq(mContext), |
| eq(mChildSessionOptions), |
| eq(mUserCbHandler), |
| eq(mMockChildSessionCallback), |
| mChildSessionSmCbCaptor.capture()); |
| IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue(); |
| |
| cb.onProcedureFinished(mMockChildSessionStateMachine); |
| mLooper.dispatchAll(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| } |
| |
| 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(mUserCbHandler), |
| any(IChildSessionCallback.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( |
| IChildSessionCallback callback, ChildSessionStateMachine sm) { |
| setupChildStateMachineFactory(sm); |
| mIkeSessionStateMachine.registerChildSessionCallback(mChildSessionOptions, callback); |
| } |
| |
| @Test |
| public void testCreateAdditionalChild() throws Exception { |
| setupIdleStateMachine(); |
| |
| IChildSessionCallback childCallback = mock(IChildSessionCallback.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(mUserCbHandler), |
| 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(); |
| |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| IkeMessage createChildRequest = mIkeMessageCaptor.getValue(); |
| |
| 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 = makeCreateChildResponse(); |
| 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); |
| } |
| |
| private IChildSessionSmCallback createChildAndGetChildSessionSmCallback( |
| ChildSessionStateMachine child, int remoteSpi) throws Exception { |
| return createChildAndGetChildSessionSmCallback( |
| child, remoteSpi, mock(IChildSessionCallback.class)); |
| } |
| |
| private IChildSessionSmCallback createChildAndGetChildSessionSmCallback( |
| ChildSessionStateMachine child, int remoteSpi, IChildSessionCallback childCallback) |
| throws Exception { |
| registerChildStateMachine(childCallback, child); |
| |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, |
| new ChildLocalRequest( |
| IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD, |
| childCallback, |
| mChildSessionOptions)); |
| mLooper.dispatchAll(); |
| |
| verify(mMockChildSessionFactoryHelper) |
| .makeChildSessionStateMachine( |
| eq(mLooper.getLooper()), |
| eq(mContext), |
| eq(mChildSessionOptions), |
| eq(mUserCbHandler), |
| eq(childCallback), |
| mChildSessionSmCbCaptor.capture()); |
| IChildSessionSmCallback cb = mChildSessionSmCbCaptor.getValue(); |
| cb.onChildSaCreated(remoteSpi, child); |
| cb.onProcedureFinished(child); |
| mLooper.dispatchAll(); |
| |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| 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), 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) { |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| IkeMessage deleteChildMessage = mIkeMessageCaptor.getValue(); |
| |
| 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 testRemoteDeleteOneChild() throws Exception { |
| ChildSessionStateMachine child = mock(ChildSessionStateMachine.class); |
| int childRemoteSpi = 11; |
| int childLocalSpi = 12; |
| |
| setupIdleStateMachine(); |
| IChildSessionSmCallback childSmCb = |
| createChildAndGetChildSessionSmCallback(child, childRemoteSpi); |
| |
| transitionToChildProcedureOngoing(); |
| |
| // Receive Delete Child Request |
| IkeDeletePayload[] inboundDelPayloads = |
| new IkeDeletePayload[] {new IkeDeletePayload(new int[] {childRemoteSpi})}; |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, |
| makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); |
| mLooper.dispatchAll(); |
| |
| // Verify received payloads |
| verifyChildReceiveDeleteRequest(child, inboundDelPayloads); |
| |
| // Outbound payload list ready |
| IkeDeletePayload outDelPayload = new IkeDeletePayload(new int[] {childLocalSpi}); |
| outboundDeleteChildPayloadsReady(childSmCb, outDelPayload, true /*isResp*/, child); |
| |
| // 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 |
| verify(mMockIkeMessageHelper, never()) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| any(IkeMessage.class)); |
| |
| // 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 { |
| ChildSessionStateMachine child = mock(ChildSessionStateMachine.class); |
| int childOneRemoteSpi = 11; |
| int childOneLocalSpi = 12; |
| |
| int childTwoRemoteSpi = 21; |
| int childTwoLocalSpi = 22; |
| |
| setupIdleStateMachine(); |
| IChildSessionSmCallback childSmCb = |
| createChildAndGetChildSessionSmCallback(child, childOneRemoteSpi); |
| childSmCb.onChildSaCreated(childTwoRemoteSpi, child); |
| |
| transitionToChildProcedureOngoing(); |
| |
| // Receive Delete Child Request |
| IkeDeletePayload[] inboundDelPayloads = |
| new IkeDeletePayload[] { |
| new IkeDeletePayload(new int[] {childOneRemoteSpi}), |
| new IkeDeletePayload(new int[] {childTwoRemoteSpi}) |
| }; |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, |
| makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); |
| mLooper.dispatchAll(); |
| |
| // Verify received payloads |
| verifyChildReceiveDeleteRequest(child, inboundDelPayloads); |
| |
| // child outbound payload list ready |
| IkeDeletePayload outDelPayload = |
| new IkeDeletePayload(new int[] {childOneLocalSpi, childTwoLocalSpi}); |
| outboundDeleteChildPayloadsReady(childSmCb, outDelPayload, true /*isResp*/, child); |
| 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 { |
| ChildSessionStateMachine child = mock(ChildSessionStateMachine.class); |
| int childRemoteSpi = 11; |
| int childLocalSpi = 12; |
| |
| int unrecognizedSpi = 2; |
| |
| setupIdleStateMachine(); |
| IChildSessionSmCallback childSmCb = |
| createChildAndGetChildSessionSmCallback(child, childRemoteSpi); |
| |
| transitionToChildProcedureOngoing(); |
| |
| // Receive Delete Child Request |
| IkeDeletePayload[] inboundDelPayloads = |
| new IkeDeletePayload[] { |
| new IkeDeletePayload(new int[] {unrecognizedSpi, childRemoteSpi}) |
| }; |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, |
| makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/)); |
| mLooper.dispatchAll(); |
| |
| // Verify received payloads |
| verifyChildReceiveDeleteRequest(child, inboundDelPayloads); |
| |
| // child outbound payload list ready |
| IkeDeletePayload outPayload = new IkeDeletePayload(new int[] {childLocalSpi}); |
| outboundDeleteChildPayloadsReady(childSmCb, outPayload, true /*isResp*/, child); |
| mLooper.dispatchAll(); |
| |
| // Verify outbound response |
| List<IkePayload> payloadList = verifyOutInfoMsgHeaderAndGetPayloads(true /*isResp*/); |
| assertEquals(1, payloadList.size()); |
| assertEquals(outPayload, ((IkeDeletePayload) payloadList.get(0))); |
| } |
| |
| @Test |
| public void testCreateIkeLocalIkeAuth() throws Exception { |
| mockIkeSetup(); |
| |
| mLooper.dispatchAll(); |
| |
| // Validate outbound IKE AUTH request |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| IkeMessage ikeAuthReqMessage = mIkeMessageCaptor.getValue(); |
| |
| IkeHeader ikeHeader = ikeAuthReqMessage.ikeHeader; |
| assertEquals(IkeHeader.EXCHANGE_TYPE_IKE_AUTH, ikeHeader.exchangeType); |
| assertFalse(ikeHeader.isResponseMsg); |
| assertTrue(ikeHeader.fromIkeInitiator); |
| |
| 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_AUTH, IkeAuthPskPayload.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)); |
| |
| // Validate inbound IKE AUTH response |
| verify(mMockIkeMessageHelper).decode(anyInt(), any(), any(), any(), any(), any()); |
| |
| 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)); |
| |
| 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)); |
| |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| } |
| |
| @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); |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound message |
| IkeMessage rekeyMsg = mIkeMessageCaptor.getValue(); |
| |
| 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(); |
| |
| // Prepare "rekeyed" SA |
| when(mMockSaRecordHelper.makeRekeyedIkeSaRecord( |
| eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any())) |
| .thenReturn(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); |
| assertEquals(mSpyLocalInitIkeSaRecord, mIkeSessionStateMachine.mLocalInitNewIkeSaRecord); |
| } |
| |
| @Test |
| public void testRekeyIkeLocalDeleteSendsRequest() throws Exception { |
| setupIdleStateMachine(); |
| |
| // Seed fake rekey data and force transition to RekeyIkeLocalDelete |
| mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord; |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_FORCE_TRANSITION, |
| mIkeSessionStateMachine.mRekeyIkeLocalDelete); |
| mLooper.dispatchAll(); |
| |
| // Verify Rekey-Delete request |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() |
| instanceof IkeSessionStateMachine.RekeyIkeLocalDelete); |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound message |
| IkeMessage delMsg = mIkeMessageCaptor.getValue(); |
| |
| 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); |
| } |
| |
| @Test |
| public void testRekeyIkeLocalDeleteHandlesResponse() throws Exception { |
| setupIdleStateMachine(); |
| |
| // Seed fake rekey data and force transition to RekeyIkeLocalDelete |
| mIkeSessionStateMachine.mLocalInitNewIkeSaRecord = mSpyLocalInitIkeSaRecord; |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_FORCE_TRANSITION, |
| mIkeSessionStateMachine.mRekeyIkeLocalDelete); |
| mLooper.dispatchAll(); |
| |
| // 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. |
| verify(mSpyCurrentIkeSaRecord).close(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord); |
| } |
| |
| @Test |
| public void testRekeyIkeLocalDeleteWithRequestOnNewSa() throws Exception { |
| setupIdleStateMachine(); |
| |
| // 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(); |
| |
| // 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. |
| verify(mSpyCurrentIkeSaRecord).close(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord); |
| } |
| |
| @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. |
| verify(mSpyCurrentIkeSaRecord).close(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyRemoteInitIkeSaRecord); |
| } |
| |
| @Test |
| public void testRekeyIkeRemoteCreate() throws Exception { |
| setupIdleStateMachine(); |
| |
| when(mMockSaRecordHelper.makeRekeyedIkeSaRecord( |
| eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any())) |
| .thenReturn(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 |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| IkeMessage rekeyCreateResp = mIkeMessageCaptor.getValue(); |
| 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); |
| } |
| |
| @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 |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| IkeMessage rekeyDeleteResp = mIkeMessageCaptor.getValue(); |
| 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. |
| verify(mSpyCurrentIkeSaRecord).close(); |
| verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, dummyDeleteIkeRequestReceivedPacket); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mCurrentIkeSaRecord); |
| } |
| |
| @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_IKE); |
| 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()); |
| verify(mMockIkeMessageHelper, never()) |
| .encryptAndEncode( |
| anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject()); |
| |
| // 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()); |
| verify(mMockIkeMessageHelper, never()) |
| .encryptAndEncode( |
| anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject()); |
| |
| // Verify final state - Idle, with new SA, and old SA closed. |
| verify(mSpyCurrentIkeSaRecord).close(); |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle); |
| assertEquals(mSpyRemoteInitIkeSaRecord, mIkeSessionStateMachine.mCurrentIkeSaRecord); |
| } |
| |
| @Test |
| public void testSimulRekey() throws Exception { |
| setupIdleStateMachine(); |
| |
| when(mMockSaRecordHelper.makeRekeyedIkeSaRecord( |
| eq(mSpyCurrentIkeSaRecord), any(), any(), any(), any())) |
| .thenReturn(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(); |
| |
| // 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); |
| assertEquals(mIkeSessionStateMachine.mCurrentIkeSaRecord, mSpyLocalInitIkeSaRecord); |
| } |
| |
| @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); |
| } |
| |
| @Test |
| public void testRetransmitterImmediatelySendsRequest() throws Exception { |
| setupIdleStateMachine(); |
| |
| IkeSocket mockIkeSocket = mock(IkeSocket.class); |
| mIkeSessionStateMachine.mIkeSocket = mockIkeSocket; |
| |
| IkeMessage mockIkeMessage = mock(IkeMessage.class); |
| |
| // Use something unique as a sentinel value |
| byte[] dummyBytes = "testRetransmitterSendsRequest".getBytes(); |
| when(mockIkeMessage.encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord))) |
| .thenReturn(dummyBytes); |
| |
| IkeSessionStateMachine.Retransmitter retransmitter = |
| mIkeSessionStateMachine.new Retransmitter(mockIkeMessage); |
| verify(mockIkeSocket).sendIkePacket(eq(dummyBytes), eq(REMOTE_ADDRESS)); |
| } |
| |
| @Test |
| public void testDeleteIkeLocalDeleteRequest() throws Exception { |
| setupIdleStateMachine(); |
| |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, |
| new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); |
| mLooper.dispatchAll(); |
| |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound message |
| IkeMessage delMsg = mIkeMessageCaptor.getValue(); |
| |
| 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(); |
| |
| ReceivedIkePacket received = makeDeleteIkeResponse(mSpyCurrentIkeSaRecord); |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received); |
| mLooper.dispatchAll(); |
| verifyIncrementLocaReqMsgId(); |
| |
| // TODO: Verify callbacks |
| |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Closed); |
| } |
| |
| @Test |
| public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception { |
| setupIdleStateMachine(); |
| |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ, |
| new LocalRequest(IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_IKE)); |
| mLooper.dispatchAll(); |
| |
| // Verify delete sent out. |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), anyObject(), eq(mSpyCurrentIkeSaRecord), anyObject()); |
| reset(mMockIkeMessageHelper); // Discard value. |
| when(mMockIkeMessageHelper.encryptAndEncode(any(), any(), any(), any())) |
| .thenReturn(new byte[0]); |
| |
| ReceivedIkePacket received = makeRekeyIkeRequest(); |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, received); |
| |
| mLooper.dispatchAll(); |
| verifyIncrementRemoteReqMsgId(); |
| |
| verify(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound response |
| IkeMessage resp = mIkeMessageCaptor.getValue(); |
| |
| IkeHeader ikeHeader = resp.ikeHeader; |
| assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType); |
| assertEquals(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA, 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(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound message |
| IkeMessage delMsg = mIkeMessageCaptor.getValue(); |
| |
| 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()); |
| |
| // TODO: Verify callbacks |
| |
| assertTrue( |
| mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Closed); |
| } |
| |
| @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(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound response |
| IkeMessage resp = mIkeMessageCaptor.getValue(); |
| 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(mMockIkeMessageHelper) |
| .encryptAndEncode( |
| anyObject(), |
| anyObject(), |
| eq(mSpyCurrentIkeSaRecord), |
| mIkeMessageCaptor.capture()); |
| |
| // Verify outbound response |
| IkeMessage resp = mIkeMessageCaptor.getValue(); |
| 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); |
| } |
| |
| @Test |
| public void testNonIdleStateDoesNotTriggerNewRequests() throws Exception { |
| setupIdleStateMachine(); |
| |
| // Force ourselves into a non-idle state |
| mIkeSessionStateMachine.sendMessage( |
| IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving); |
| mLooper.dispatchAll(); |
| verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any()); |
| |
| // 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 |
| verify(mMockIkeMessageHelper, never()).encryptAndEncode(any(), any(), any(), any()); |
| 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); |
| verify(mMockIkeMessageHelper, times(1)).encryptAndEncode(any(), any(), any(), any()); |
| } |
| |
| @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(); |
| |
| IChildSessionCallback cb = mock(IChildSessionCallback.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(mUserCbHandler), |
| 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(IChildSessionCallback.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(); |
| |
| IChildSessionCallback cb = mock(IChildSessionCallback.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(); |
| |
| ChildSessionStateMachine child = mock(ChildSessionStateMachine.class); |
| IChildSessionCallback childCb = mock(IChildSessionCallback.class); |
| IChildSessionSmCallback smCb = |
| createChildAndGetChildSessionSmCallback(child, 0 /* placeholder */, childCb); |
| |
| smCb.onChildSessionClosed(childCb); |
| |
| synchronized (mIkeSessionStateMachine.mChildCbToSessions) { |
| assertFalse(mIkeSessionStateMachine.mChildCbToSessions.containsKey(childCb)); |
| } |
| } |
| } |