blob: 819c32801afa0347b03e062fe54b87ad85427b75 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ike.ikev2;
import static com.android.ike.ikev2.IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE;
import static com.android.ike.ikev2.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET;
import static com.android.ike.ikev2.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
import static com.android.ike.ikev2.IkeSessionStateMachine.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
import static com.android.ike.ikev2.IkeSessionStateMachine.RETRY_INTERVAL_MS;
import static com.android.ike.ikev2.IkeSessionStateMachine.SA_SOFT_LIFETIME_MS;
import static com.android.ike.ikev2.IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
import static com.android.ike.ikev2.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
import static com.android.ike.ikev2.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
import static com.android.ike.ikev2.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
import static com.android.ike.ikev2.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_AUTH;
import static com.android.ike.ikev2.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
import static com.android.ike.ikev2.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.os.test.TestLooper;
import android.telephony.TelephonyManager;
import com.android.ike.TestUtils;
import com.android.ike.eap.EapAuthenticator;
import com.android.ike.eap.EapSessionConfig;
import com.android.ike.eap.IEapCallback;
import com.android.ike.ikev2.ChildSessionStateMachine.IChildSessionSmCallback;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.ChildSessionFactoryHelper;
import com.android.ike.ikev2.ChildSessionStateMachineFactory.IChildSessionFactoryHelper;
import com.android.ike.ikev2.IkeLocalRequestScheduler.ChildLocalRequest;
import com.android.ike.ikev2.IkeLocalRequestScheduler.LocalRequest;
import com.android.ike.ikev2.IkeSessionStateMachine.IkeSecurityParameterIndex;
import com.android.ike.ikev2.IkeSessionStateMachine.ReceivedIkePacket;
import com.android.ike.ikev2.SaRecord.ISaRecordHelper;
import com.android.ike.ikev2.SaRecord.IkeSaRecord;
import com.android.ike.ikev2.SaRecord.IkeSaRecordConfig;
import com.android.ike.ikev2.SaRecord.SaRecordHelper;
import com.android.ike.ikev2.crypto.IkeCipher;
import com.android.ike.ikev2.crypto.IkeMacIntegrity;
import com.android.ike.ikev2.crypto.IkeMacPrf;
import com.android.ike.ikev2.exceptions.AuthenticationFailedException;
import com.android.ike.ikev2.exceptions.IkeException;
import com.android.ike.ikev2.exceptions.IkeInternalException;
import com.android.ike.ikev2.exceptions.IkeProtocolException;
import com.android.ike.ikev2.exceptions.InvalidSyntaxException;
import com.android.ike.ikev2.exceptions.NoValidProposalChosenException;
import com.android.ike.ikev2.exceptions.UnsupportedCriticalPayloadException;
import com.android.ike.ikev2.message.IkeAuthDigitalSignPayload;
import com.android.ike.ikev2.message.IkeAuthPayload;
import com.android.ike.ikev2.message.IkeAuthPskPayload;
import com.android.ike.ikev2.message.IkeCertX509CertPayload;
import com.android.ike.ikev2.message.IkeDeletePayload;
import com.android.ike.ikev2.message.IkeEapPayload;
import com.android.ike.ikev2.message.IkeHeader;
import com.android.ike.ikev2.message.IkeIdPayload;
import com.android.ike.ikev2.message.IkeInformationalPayload;
import com.android.ike.ikev2.message.IkeKePayload;
import com.android.ike.ikev2.message.IkeMessage;
import com.android.ike.ikev2.message.IkeMessage.DecodeResult;
import com.android.ike.ikev2.message.IkeMessage.DecodeResultOk;
import com.android.ike.ikev2.message.IkeMessage.DecodeResultPartial;
import com.android.ike.ikev2.message.IkeMessage.DecodeResultProtectedError;
import com.android.ike.ikev2.message.IkeMessage.DecodeResultUnprotectedError;
import com.android.ike.ikev2.message.IkeMessage.IIkeMessageHelper;
import com.android.ike.ikev2.message.IkeMessage.IkeMessageHelper;
import com.android.ike.ikev2.message.IkeNoncePayload;
import com.android.ike.ikev2.message.IkeNotifyPayload;
import com.android.ike.ikev2.message.IkePayload;
import com.android.ike.ikev2.message.IkeSaPayload;
import com.android.ike.ikev2.message.IkeSaPayload.DhGroupTransform;
import com.android.ike.ikev2.message.IkeSaPayload.EncryptionTransform;
import com.android.ike.ikev2.message.IkeSaPayload.IntegrityTransform;
import com.android.ike.ikev2.message.IkeSaPayload.PrfTransform;
import com.android.ike.ikev2.message.IkeSkfPayload;
import com.android.ike.ikev2.message.IkeTestUtils;
import com.android.ike.ikev2.message.IkeTsPayload;
import com.android.ike.ikev2.testutils.CertUtils;
import com.android.ike.ikev2.testutils.MockIpSecTestUtils;
import com.android.ike.ikev2.utils.Retransmitter;
import com.android.ike.ikev2.utils.Retransmitter.IBackoffTimeoutCalculator;
import com.android.ike.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;
}
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);
}
}