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/ike/IkeSessionStateMachineTest.java | |
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/ike/IkeSessionStateMachineTest.java')
-rw-r--r-- | tests/iketests/src/java/com/android/internal/net/ipsec/ike/IkeSessionStateMachineTest.java | 4036 |
1 files changed, 4036 insertions, 0 deletions
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); + } +} |