blob: 9b00e12d6f2bf444d3967e6cb426448321812a51 [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.internal.net.ipsec.test.ike;
import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE;
import static android.net.ipsec.test.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
import static android.net.ipsec.test.ike.IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH;
import static android.net.ipsec.test.ike.IkeSessionParams.IKE_OPTION_FORCE_PORT_4500;
import static android.net.ipsec.test.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_CHILD_SA_NOT_FOUND;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_INTERNAL_ADDRESS_FAILURE;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_INVALID_SYNTAX;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_ADDITIONAL_SAS;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
import static android.net.ipsec.test.ike.exceptions.IkeProtocolException.ERROR_TYPE_UNSUPPORTED_CRITICAL_PAYLOAD;
import static android.net.ipsec.test.ike.ike3gpp.Ike3gppBackoffTimer.ERROR_TYPE_NETWORK_FAILURE;
import static android.net.ipsec.test.ike.ike3gpp.Ike3gppBackoffTimer.ERROR_TYPE_NO_APN_SUBSCRIPTION;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static com.android.internal.net.TestUtils.createMockRandomFactory;
import static com.android.internal.net.eap.test.EapResult.EapResponse.RESPONSE_FLAG_EAP_AKA_SERVER_AUTHENTICATED;
import static com.android.internal.net.ipsec.test.ike.AbstractSessionStateMachine.RETRY_INTERVAL_MS;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.CMD_FORCE_TRANSITION;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.IkeAuthData;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.IkeInitData;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.InitialSetupData;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.RETRY_INTERVAL_MS;
import static com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.TEMP_FAILURE_RETRY_TIMEOUT_MS;
import static com.android.internal.net.ipsec.test.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_BACKOFF_TIMER;
import static com.android.internal.net.ipsec.test.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_DEVICE_IDENTITY;
import static com.android.internal.net.ipsec.test.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_N1_MODE_CAPABILITY;
import static com.android.internal.net.ipsec.test.ike.ike3gpp.Ike3gppExtensionExchange.NOTIFY_TYPE_N1_MODE_INFORMATION;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_APPLICATION_VERSION;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_ADDRESS;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP4_NETMASK;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_INTERNAL_IP6_ADDRESS;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_IP4_PCSCF;
import static com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload.CONFIG_ATTR_IP6_PCSCF;
import static com.android.internal.net.ipsec.test.ike.message.IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA;
import static com.android.internal.net.ipsec.test.ike.message.IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
import static com.android.internal.net.ipsec.test.ike.message.IkeMessage.IKE_EXCHANGE_SUBTYPE_DELETE_CHILD;
import static com.android.internal.net.ipsec.test.ike.message.IkeMessage.IKE_EXCHANGE_SUBTYPE_REKEY_CHILD;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_COOKIE;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_COOKIE2;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_EAP_ONLY_AUTHENTICATION;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_IKEV2_FRAGMENTATION_SUPPORTED;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_INITIAL_CONTACT;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_MOBIKE_SUPPORTED;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_SIGNATURE_HASH_ALGORITHMS;
import static com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload.NOTIFY_TYPE_UPDATE_SA_ADDRESSES;
import static com.android.internal.net.ipsec.test.ike.message.IkePayload.PAYLOAD_TYPE_AUTH;
import static com.android.internal.net.ipsec.test.ike.message.IkePayload.PAYLOAD_TYPE_KE;
import static com.android.internal.net.ipsec.test.ike.message.IkePayload.PAYLOAD_TYPE_NONCE;
import static com.android.internal.net.ipsec.test.ike.message.IkePayload.PAYLOAD_TYPE_NOTIFY;
import static com.android.internal.net.ipsec.test.ike.message.IkePayload.PAYLOAD_TYPE_SA;
import static com.android.internal.net.ipsec.test.ike.net.IkeConnectionController.NAT_DETECTED;
import static com.android.internal.net.ipsec.test.ike.net.IkeConnectionController.NAT_TRAVERSAL_SUPPORT_NOT_CHECKED;
import static com.android.internal.net.ipsec.test.ike.net.IkeConnectionController.NAT_TRAVERSAL_UNSUPPORTED;
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.assertSame;
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.anyLong;
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.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
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.annotation.Nullable;
import android.net.LinkAddress;
import android.net.Network;
import android.net.eap.test.EapSessionConfig;
import android.net.ipsec.test.ike.ChildSaProposal;
import android.net.ipsec.test.ike.ChildSessionCallback;
import android.net.ipsec.test.ike.ChildSessionParams;
import android.net.ipsec.test.ike.IkeFqdnIdentification;
import android.net.ipsec.test.ike.IkeIdentification;
import android.net.ipsec.test.ike.IkeIpv4AddrIdentification;
import android.net.ipsec.test.ike.IkeManager;
import android.net.ipsec.test.ike.IkeSaProposal;
import android.net.ipsec.test.ike.IkeSessionCallback;
import android.net.ipsec.test.ike.IkeSessionConfiguration;
import android.net.ipsec.test.ike.IkeSessionConnectionInfo;
import android.net.ipsec.test.ike.IkeSessionParams;
import android.net.ipsec.test.ike.SaProposal;
import android.net.ipsec.test.ike.TransportModeChildSessionParams;
import android.net.ipsec.test.ike.TunnelModeChildSessionParams;
import android.net.ipsec.test.ike.exceptions.AuthenticationFailedException;
import android.net.ipsec.test.ike.exceptions.IkeException;
import android.net.ipsec.test.ike.exceptions.IkeInternalException;
import android.net.ipsec.test.ike.exceptions.IkeNetworkLostException;
import android.net.ipsec.test.ike.exceptions.IkeProtocolException;
import android.net.ipsec.test.ike.exceptions.InvalidSyntaxException;
import android.net.ipsec.test.ike.exceptions.NoValidProposalChosenException;
import android.net.ipsec.test.ike.exceptions.UnrecognizedIkeProtocolException;
import android.net.ipsec.test.ike.exceptions.UnsupportedCriticalPayloadException;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppBackoffTimer;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppData;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppExtension;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppExtension.Ike3gppDataListener;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppN1ModeInformation;
import android.net.ipsec.test.ike.ike3gpp.Ike3gppParams;
import android.os.test.TestLooper;
import android.telephony.TelephonyManager;
import androidx.test.filters.SdkSuppress;
import com.android.internal.net.TestUtils;
import com.android.internal.net.eap.test.EapAuthenticator;
import com.android.internal.net.eap.test.IEapCallback;
import com.android.internal.net.ipsec.test.ike.ChildSessionStateMachine.IChildSessionSmCallback;
import com.android.internal.net.ipsec.test.ike.IkeLocalRequestScheduler.ChildLocalRequest;
import com.android.internal.net.ipsec.test.ike.IkeLocalRequestScheduler.IkeLocalRequest;
import com.android.internal.net.ipsec.test.ike.IkeLocalRequestScheduler.LocalRequestFactory;
import com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.CreateIkeLocalIkeAuth;
import com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.CreateIkeLocalIkeAuthInEap;
import com.android.internal.net.ipsec.test.ike.IkeSessionStateMachine.ReceivedIkePacket;
import com.android.internal.net.ipsec.test.ike.SaRecord.ISaRecordHelper;
import com.android.internal.net.ipsec.test.ike.SaRecord.IkeSaRecord;
import com.android.internal.net.ipsec.test.ike.SaRecord.IkeSaRecordConfig;
import com.android.internal.net.ipsec.test.ike.SaRecord.SaLifetimeAlarmScheduler;
import com.android.internal.net.ipsec.test.ike.SaRecord.SaRecordHelper;
import com.android.internal.net.ipsec.test.ike.crypto.IkeCipher;
import com.android.internal.net.ipsec.test.ike.crypto.IkeMacIntegrity;
import com.android.internal.net.ipsec.test.ike.crypto.IkeMacPrf;
import com.android.internal.net.ipsec.test.ike.keepalive.IkeNattKeepalive;
import com.android.internal.net.ipsec.test.ike.message.IkeAuthDigitalSignPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeAuthPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeAuthPskPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeCertX509CertPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeConfigPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeDeletePayload;
import com.android.internal.net.ipsec.test.ike.message.IkeEapPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeHeader;
import com.android.internal.net.ipsec.test.ike.message.IkeIdPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeInformationalPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeKePayload;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.DecodeResult;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.DecodeResultOk;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.DecodeResultPartial;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.DecodeResultProtectedError;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.DecodeResultUnprotectedError;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.IIkeMessageHelper;
import com.android.internal.net.ipsec.test.ike.message.IkeMessage.IkeMessageHelper;
import com.android.internal.net.ipsec.test.ike.message.IkeNoncePayload;
import com.android.internal.net.ipsec.test.ike.message.IkeNotifyPayload;
import com.android.internal.net.ipsec.test.ike.message.IkePayload;
import com.android.internal.net.ipsec.test.ike.message.IkeSaPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeSaPayload.DhGroupTransform;
import com.android.internal.net.ipsec.test.ike.message.IkeSaPayload.EncryptionTransform;
import com.android.internal.net.ipsec.test.ike.message.IkeSaPayload.IntegrityTransform;
import com.android.internal.net.ipsec.test.ike.message.IkeSaPayload.PrfTransform;
import com.android.internal.net.ipsec.test.ike.message.IkeSkfPayload;
import com.android.internal.net.ipsec.test.ike.message.IkeTestUtils;
import com.android.internal.net.ipsec.test.ike.message.IkeTsPayload;
import com.android.internal.net.ipsec.test.ike.net.IkeConnectionController;
import com.android.internal.net.ipsec.test.ike.net.IkeDefaultNetworkCallback;
import com.android.internal.net.ipsec.test.ike.net.IkeNetworkCallbackBase;
import com.android.internal.net.ipsec.test.ike.net.IkeSpecificNetworkCallback;
import com.android.internal.net.ipsec.test.ike.testmode.DeterministicSecureRandom;
import com.android.internal.net.ipsec.test.ike.testutils.CertUtils;
import com.android.internal.net.ipsec.test.ike.utils.IkeAlarm.IkeAlarmConfig;
import com.android.internal.net.ipsec.test.ike.utils.IkeSecurityParameterIndex;
import com.android.internal.net.ipsec.test.ike.utils.IkeSpiGenerator;
import com.android.internal.net.ipsec.test.ike.utils.RandomnessFactory;
import com.android.internal.net.ipsec.test.ike.utils.State;
import com.android.internal.net.utils.test.Log;
import com.android.internal.util.HexDump;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;
public final class IkeSessionStateMachineTest extends IkeSessionTestBase {
private static final String TAG = "IkeSessionStateMachineTest";
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 INVALID_KE_PAYLOAD_HEX_STRING = "0000000a00000011000e";
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 =
"2900001c00004005d915368ca036004cb578ae3e3fb268509aeab190";
private static final String FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING = "290000080000402e";
private static final String SIGNATURE_HASH_SUPPORTED_PAYLOAD_HEX_STRING =
"2b0000100000402f0001000200030004";
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 ID_PAYLOAD_RESPONDER_FQDN_HEX_STRING =
"2700001702000000696B652E616E64726F69642E6E6574";
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 VENDOR_ID_PAYLOAD_HEX_STRING =
"0000001852656d6f74652056656e646f72204944204f6e65";
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 String CP_PAYLOAD_HEX_STRING =
"210000810200000000080011260010111067a17d000000000a"
+ "f68e8640000a0010200148880067ff000643000d000000000"
+ "00a0010200148880066ff000645000d0000000000150010200"
+ "148880006713a00f10104000000050015001020014888000671"
+ "3a00f101040000008900150010200148880005713a00e00104000000c9";
private static final String PCSCF_IPV6_ADDRESS1 = "2001:4888:6:713a:f1:104:0:5";
private static final String PCSCF_IPV6_ADDRESS2 = "2001:4888:6:713a:f1:104:0:89";
private static final String PCSCF_IPV6_ADDRESS3 = "2001:4888:5:713a:e0:104:0:c9";
private static final byte[] EAP_DUMMY_MSG = "EAP Message".getBytes();
private static final int EAP_RESPONSE_FLAG_MASK_WITH_EAP_AKA_SERVER_AUTHENTICATED =
(1 << RESPONSE_FLAG_EAP_AKA_SERVER_AUTHENTICATED);
private static final int EAP_RESPONSE_FLAGS_NOT_SET = 0;
private static final byte[] REMOTE_VENDOR_ID_ONE = "Remote Vendor ID One".getBytes();
private static final byte[] REMOTE_VENDOR_ID_TWO = "Remote Vendor ID Two".getBytes();
private static final IkeIdentification LOCAL_ID_IPV4 =
new IkeIpv4AddrIdentification((Inet4Address) LOCAL_ADDRESS);
private static final IkeIdentification REMOTE_ID_FQDN =
new IkeFqdnIdentification("server.test.android.net");
private static final IkeIdentification REMOTE_ID_IPV4 =
new IkeIpv4AddrIdentification((Inet4Address) REMOTE_ADDRESS);
private static final byte PDU_SESSION_ID = (byte) 0x7B;
private static final String N1_MODE_CAPABILITY_PAYLOAD_DATA = "017B";
private static final byte[] SNSSAI = {(byte) 456};
private static final byte BACKOFF_TIMER = (byte) 0xAF;
private static final byte[] BACKOFF_TIMER_DATA = {0x01, BACKOFF_TIMER};
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 EAP_SIM_SUB_ID = 1;
private static final int PAYLOAD_TYPE_UNSUPPORTED = 127;
private static final int COOKIE_DATA_LEN = 64;
private static final int COOKIE2_DATA_LEN = 64;
private static final byte[] COOKIE_DATA = new byte[COOKIE_DATA_LEN];
private static final byte[] COOKIE2_DATA = new byte[COOKIE2_DATA_LEN];
private static final int NATT_KEEPALIVE_DELAY = 20;
private static final String DEVICE_IDENTITY_IMEI = "123456789123456";
private static final byte[] DEVICE_IDENTITY_PAYLOAD_IMEI =
HexDump.hexStringToByteArray("00090121436587193254F6");
static {
new Random().nextBytes(COOKIE_DATA);
new Random().nextBytes(COOKIE2_DATA);
}
private static final long RETRANSMIT_BACKOFF_TIMEOUT_MS = 5000L;
private static final IkeSpiGenerator IKE_SPI_GENERATOR =
new IkeSpiGenerator(createMockRandomFactory());
private static final Ike3gppParams IKE_3GPP_PARAMS =
new Ike3gppParams.Builder().setPduSessionId(PDU_SESSION_ID).build();
private IkeUdpEncapSocket mMockIkeUdpEncapSocket;
private IkeUdp6WithEncapPortSocket mMockIkeUdp6WithEncapPortSocket;
private IkeUdp4Socket mMockIkeUdp4Socket;
private IkeUdp6Socket mMockIkeUdp6Socket;
private IkeSocket mMockCurrentIkeSocket;
private LinkAddress mMockLinkAddressGlobalV6;
private IkeNattKeepalive mMockIkeNattKeepalive;
private TestLooper mLooper;
private IkeSessionStateMachine mIkeSessionStateMachine;
private byte[] mPsk;
private ChildSessionParams mChildSessionParams;
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 ChildSessionStateMachine mMockChildSessionStateMachine;
private IChildSessionSmCallback mDummyChildSmCallback;
private IkeSaRecord mSpyCurrentIkeSaRecord;
private IkeSaRecord mSpyLocalInitIkeSaRecord;
private IkeSaRecord mSpyRemoteInitIkeSaRecord;
private Log mSpyIkeLog;
private int mExpectedCurrentSaLocalReqMsgId;
private int mExpectedCurrentSaRemoteReqMsgId;
private IkeSessionStateMachine.Dependencies mSpyDeps;
private EapSessionConfig mEapSessionConfig;
private EapSessionConfig mEapSessionConfigEapAka;
private EapAuthenticator mMockEapAuthenticator;
private IkeConnectionController mSpyIkeConnectionCtrl;
private Ike3gppDataListener mMockIke3gppDataListener;
private Ike3gppExtension mIke3gppExtension;
private X509Certificate mRootCertificate;
private X509Certificate mServerEndCertificate;
private PrivateKey mUserPrivateKey;
private X509Certificate mUserEndCert;
private LocalRequestFactory mLocalRequestFactory;
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(List<IkePayload> payloadList)
throws Exception {
long dummyInitSpi = 1L;
long dummyRespSpi = 2L;
return makeDummyUnencryptedReceivedIkePacket(
dummyInitSpi,
dummyRespSpi,
IkeHeader.EXCHANGE_TYPE_IKE_SA_INIT,
true /*isResp*/,
false /*fromIkeInit*/,
payloadList);
}
private ReceivedIkePacket makeDummyReceivedIkeInitRespPacket(
List<Integer> payloadTypeList, List<String> payloadHexStringList) throws Exception {
List<IkePayload> payloadList =
hexStrListToIkePayloadList(
payloadTypeList, payloadHexStringList, true /* isResp */);
return makeDummyReceivedIkeInitRespPacket(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];
doReturn(new DecodeResultOk(dummyIkeMessage, dummyIkePacketBytes))
.when(mMockIkeMessageHelper)
.decode(0, dummyIkeMessage.ikeHeader, 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];
doReturn(new DecodeResultProtectedError(exception, dummyPacket))
.when(mMockIkeMessageHelper)
.decode(anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any());
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];
doReturn(new DecodeResultUnprotectedError(exception))
.when(mMockIkeMessageHelper)
.decode(anyInt(), any(), any(), eq(ikeSaRecord), eq(header), any(), any());
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) {
doReturn(result)
.when(mMockIkeMessageHelper)
.decode(
anyInt(),
any(),
any(),
eq(ikeSaRecord),
eq(header),
any(),
eq(collectedFrags));
}
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 ArrayList<>();
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(
IKE_SPI_GENERATOR.allocateSpi(initAddress, initSpi),
IKE_SPI_GENERATOR.allocateSpi(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),
mock(SaLifetimeAlarmScheduler.class));
}
private void mockScheduleRekey(SaLifetimeAlarmScheduler mockSaLifetimeAlarmScheduler) {
IkeLocalRequest rekeyReq =
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE);
doAnswer(
(invocation) -> {
mIkeSessionStateMachine.sendMessageDelayed(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
rekeyReq,
mIkeSessionStateMachine.mIkeSessionParams
.getSoftLifetimeMsInternal());
return null;
})
.when(mockSaLifetimeAlarmScheduler)
.scheduleLifetimeExpiryAlarm(anyString());
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
mSpyIkeLog = TestUtils.makeSpyLogThrowExceptionForWtf(TAG);
IkeManager.setIkeLog(mSpyIkeLog);
mEapSessionConfig =
new EapSessionConfig.Builder()
.setEapSimConfig(EAP_SIM_SUB_ID, TelephonyManager.APPTYPE_USIM)
.build();
setupLocalAddressForNetwork(mMockDefaultNetwork, LOCAL_ADDRESS);
setupLocalAddressForNetwork(mMockDefaultNetwork, LOCAL_ADDRESS_V6);
mMockLinkAddressGlobalV6 = mock(LinkAddress.class);
when(mMockLinkAddressGlobalV6.getAddress()).thenReturn(UPDATED_LOCAL_ADDRESS_V6);
when(mMockLinkAddressGlobalV6.isGlobalPreferred()).thenReturn(true);
mMockEapAuthenticator = mock(EapAuthenticator.class);
mMockChildSessionStateMachine = mock(ChildSessionStateMachine.class);
mRootCertificate = CertUtils.createCertFromPemFile("self-signed-ca-a.pem");
mServerEndCertificate = CertUtils.createCertFromPemFile("end-cert-a.pem");
mUserEndCert = CertUtils.createCertFromPemFile("end-cert-b.pem");
mUserPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("end-cert-key-b.key");
mPsk = TestUtils.hexStringToByteArray(PSK_HEX_STRING);
mChildSessionParams = buildChildSessionParams();
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);
mLocalRequestFactory = new LocalRequestFactory();
mLooper = new TestLooper();
// Setup state machine
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsPsk(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;
mMockIke3gppDataListener = mock(Ike3gppDataListener.class);
}
@After
public void tearDown() throws Exception {
mIkeSessionStateMachine.killSession();
mLooper.dispatchAll();
mIkeSessionStateMachine.setDbg(false);
mSpyCurrentIkeSaRecord.close();
mSpyLocalInitIkeSaRecord.close();
mSpyRemoteInitIkeSaRecord.close();
IkeManager.resetIkeLog();
IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
SaRecord.setSaRecordHelper(new SaRecordHelper());
}
private void injectChildSessionInSpyDeps(
IkeSessionStateMachine.Dependencies spyDeps,
ChildSessionStateMachine child,
ChildSessionCallback childCb) {
doReturn(child)
.when(spyDeps)
.newChildSessionStateMachine(
any(IkeContext.class),
any(ChildSessionStateMachine.Config.class),
eq(childCb),
any(IChildSessionSmCallback.class));
}
private IkeSessionStateMachine.Dependencies buildSpyDepsWithChildSession(
ChildSessionStateMachine child, ChildSessionCallback childCb) {
IkeSessionStateMachine.Dependencies spyDeps =
spy(new IkeSessionStateMachine.Dependencies());
doReturn(mMockEapAuthenticator)
.when(spyDeps)
.newEapAuthenticator(
any(IkeContext.class),
any(IEapCallback.class),
any(EapSessionConfig.class));
doReturn(mSpyIkeConnectionCtrl)
.when(spyDeps)
.newIkeConnectionController(
any(IkeContext.class), any(IkeConnectionController.Config.class));
injectChildSessionInSpyDeps(spyDeps, child, childCb);
return spyDeps;
}
private IkeSessionStateMachine makeAndStartIkeSession(IkeSessionParams ikeParams)
throws Exception {
return makeAndStartIkeSession(ikeParams, LOCAL_ADDRESS, REMOTE_ADDRESS);
}
private void setupIkeConnectionCtrlCbWithIkeSession(
IkeConnectionController.Callback mockCallback, IkeSessionStateMachine ikeSession) {
doAnswer(
(invocation) -> {
ikeSession.onUnderlyingNetworkUpdated();
return null;
})
.when(mockCallback)
.onUnderlyingNetworkUpdated();
doAnswer(
(invocation) -> {
ikeSession.onUnderlyingNetworkDied(invocation.getArgument(0));
return null;
})
.when(mockCallback)
.onUnderlyingNetworkDied(any());
doAnswer(
(invocation) -> {
ikeSession.onError(invocation.getArgument(0));
return null;
})
.when(mockCallback)
.onError(any());
}
private IkeSessionStateMachine makeAndStartIkeSession(
IkeSessionParams ikeParams,
InetAddress localAddress,
InetAddress expectedRemoteAddress)
throws Exception {
IkeContext ikeContext =
new IkeSessionStateMachine.Dependencies()
.newIkeContext(
mLooper.getLooper(), mSpyContext, ikeParams.getConfiguredNetwork());
IkeConnectionController.Callback mockIkeConnectionCtrlCb =
mock(IkeConnectionController.Callback.class);
IkeConnectionController.Dependencies spyIkeConnectionCtrlDeps =
spy(new IkeConnectionController.Dependencies());
doReturn(mMockIkeLocalAddressGenerator)
.when(spyIkeConnectionCtrlDeps)
.newIkeLocalAddressGenerator();
mMockIkeNattKeepalive = mock(IkeNattKeepalive.class);
doAnswer(
(invocation) -> {
mMockIkeNattKeepalive.start();
return mMockIkeNattKeepalive;
})
.when(spyIkeConnectionCtrlDeps)
.newIkeNattKeepalive(any(), any(), any(), any(), any(), any());
// Setup socket instances used by the IkeSessionStateMachine
mMockIkeUdp4Socket = newMockIkeSocket(IkeUdp4Socket.class);
mMockIkeUdp6Socket = newMockIkeSocket(IkeUdp6Socket.class);
mMockIkeUdpEncapSocket = newMockIkeSocket(IkeUdpEncapSocket.class);
mMockIkeUdp6WithEncapPortSocket = newMockIkeSocket(IkeUdp6WithEncapPortSocket.class);
doReturn(mMockIkeUdp4Socket)
.when(spyIkeConnectionCtrlDeps)
.newIkeUdp4Socket(any(), any(), any());
doReturn(mMockIkeUdp6Socket)
.when(spyIkeConnectionCtrlDeps)
.newIkeUdp6Socket(any(), any(), any());
doReturn(mMockIkeUdpEncapSocket)
.when(spyIkeConnectionCtrlDeps)
.newIkeUdpEncapSocket(any(), any(), any(), any());
doReturn(mMockIkeUdp6WithEncapPortSocket)
.when(spyIkeConnectionCtrlDeps)
.newIkeUdp6WithEncapPortSocket(any(), any(), any());
mSpyIkeConnectionCtrl =
new IkeConnectionController(
ikeContext,
new IkeConnectionController.Config(
ikeParams, mock(IkeAlarmConfig.class), mockIkeConnectionCtrlCb),
spyIkeConnectionCtrlDeps);
mSpyDeps =
buildSpyDepsWithChildSession(
mMockChildSessionStateMachine, mMockChildSessionCallback);
IkeSessionStateMachine ikeSession =
new IkeSessionStateMachine(
mLooper.getLooper(),
mSpyContext,
mIpSecManager,
mMockConnectManager,
ikeParams,
mChildSessionParams,
mSpyUserCbExecutor,
mMockIkeSessionCallback,
mMockChildSessionCallback,
mSpyDeps);
setupIkeConnectionCtrlCbWithIkeSession(mockIkeConnectionCtrlCb, ikeSession);
ikeSession.setDbg(true);
mLooper.dispatchAll();
mMockCurrentIkeSocket = mSpyIkeConnectionCtrl.getIkeSocket();
assertEquals(expectedRemoteAddress, mSpyIkeConnectionCtrl.getRemoteAddress());
if (ikeParams.getConfiguredNetwork() == null) {
verify(mMockConnectManager, atLeast(1)).getActiveNetwork();
} else {
verify(mMockConnectManager, never()).getActiveNetwork();
}
return ikeSession;
}
public static IkeSaProposal buildSaProposal() throws Exception {
return buildSaProposalCommon().addDhGroup(SaProposal.DH_GROUP_2048_BIT_MODP).build();
}
private static IkeSaProposal buildNegotiatedSaProposal() throws Exception {
return buildSaProposalCommon().build();
}
private static IkeSaProposal.Builder buildSaProposalCommon() 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);
}
private IkeSessionParams.Builder buildIkeSessionParamsCommon() throws Exception {
return new IkeSessionParams.Builder(mMockConnectManager)
.setServerHostname(REMOTE_HOSTNAME)
.addSaProposal(buildSaProposal())
.setLocalIdentification(LOCAL_ID_IPV4)
.setRemoteIdentification(REMOTE_ID_FQDN)
.addPcscfServerRequest(AF_INET)
.addPcscfServerRequest(AF_INET6)
.setRetransmissionTimeoutsMillis(
new int[] {5000, 10000, 20000, 30000, 40000, 50000});
}
private IkeSessionParams buildIkeSessionParams() throws Exception {
return buildIkeSessionParamsCommon().setAuthPsk(mPsk).build();
}
private IkeSessionParams buildIkeSessionParamsPsk(byte[] psk) throws Exception {
return buildIkeSessionParamsCommon().setAuthPsk(psk).build();
}
private IkeSessionParams buildIkeSessionParamsEap() throws Exception {
return buildIkeSessionParamsCommon()
.setAuthEap(mRootCertificate, mEapSessionConfig)
.build();
}
private IkeSessionParams buildIkeSessionParamsEapAkaWithDeviceIdentity() throws Exception {
Ike3gppParams ike3gppParams =
new Ike3gppParams.Builder().setMobileDeviceIdentity(DEVICE_IDENTITY_IMEI).build();
Ike3gppExtension ike3gppExtension =
new Ike3gppExtension(ike3gppParams, mock(Ike3gppDataListener.class));
mEapSessionConfigEapAka =
new EapSessionConfig.Builder()
.setEapAkaConfig(0, TelephonyManager.APPTYPE_ISIM)
.build();
return buildIkeSessionParamsCommon()
.setAuthEap(mock(X509Certificate.class), mEapSessionConfigEapAka)
.setIke3gppExtension(ike3gppExtension)
.addIkeOption(IKE_OPTION_EAP_ONLY_AUTH)
.build();
}
private IkeSessionParams buildIkeSessionParamsDigitalSignature() throws Exception {
return buildIkeSessionParamsCommon()
.setAuthDigitalSignature(mRootCertificate, mUserEndCert, mUserPrivateKey)
.build();
}
private IkeSessionParams buildIkeSessionParamsIke3gppExtension(byte pduSessionId)
throws Exception {
Ike3gppExtension ike3gppExtension =
new Ike3gppExtension(
new Ike3gppParams.Builder().setPduSessionId(pduSessionId).build(),
mMockIke3gppDataListener);
return buildIkeSessionParamsCommon()
.setAuthPsk(mPsk)
.setIke3gppExtension(ike3gppExtension)
.build();
}
private IkeSessionParams buildIkeSessionParamsWithIkeOptions(int... ikeOptions)
throws Exception {
IkeSessionParams.Builder builder = buildIkeSessionParamsCommon().setAuthPsk(mPsk);
for (int option : ikeOptions) {
builder.addIkeOption(option);
}
return builder.build();
}
private ChildSessionParams buildChildSessionParams() 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 TunnelModeChildSessionParams.Builder()
.addSaProposal(saProposal)
.addInternalAddressRequest(AF_INET)
.addInternalAddressRequest(AF_INET6)
.build();
}
// Common IKE INIT response
private ReceivedIkePacket makeIkeInitResponse() throws Exception {
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NOTIFY);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_VENDOR);
payloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
payloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
payloadHexStringList.add(FRAGMENTATION_SUPPORTED_PAYLOAD_HEX_STRING);
payloadHexStringList.add(SIGNATURE_HASH_SUPPORTED_PAYLOAD_HEX_STRING);
payloadHexStringList.add(VENDOR_ID_PAYLOAD_HEX_STRING);
return makeIkeInitResponseWithRequiredPayloads(payloadTypeList, payloadHexStringList);
}
// Simplest IKE INIT response that does not include any optional payloads
private ReceivedIkePacket makeIkeInitResponseWithRequiredPayloads(
List<Integer> optionalPayloadTypes, List<String> optionalPayloadHexStrings)
throws Exception {
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_SA);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_KE);
payloadTypeList.add(IkePayload.PAYLOAD_TYPE_NONCE);
payloadTypeList.addAll(optionalPayloadTypes);
payloadHexStringList.add(IKE_SA_PAYLOAD_HEX_STRING);
payloadHexStringList.add(KE_PAYLOAD_HEX_STRING);
payloadHexStringList.add(NONCE_RESP_PAYLOAD_HEX_STRING);
payloadHexStringList.addAll(optionalPayloadHexStrings);
return makeDummyReceivedIkeInitRespPacket(
payloadTypeList,
payloadHexStringList);
}
private List<IkePayload> getIkeAuthPayloadListWithChildPayloads(
List<IkePayload> authRelatedPayloads) throws Exception {
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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 ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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 ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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 ArrayList<>(),
new ArrayList<>());
}
private ReceivedIkePacket makeDpdIkeRequest(IkeSaRecord saRecord) throws Exception {
return makeDummyEncryptedReceivedIkePacket(
saRecord,
IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
false /*isResp*/,
new ArrayList<>(),
new ArrayList<>());
}
private ReceivedIkePacket makeDpdIkeRequest(int msgId, byte[] dummyIkePacketBytes)
throws Exception {
return makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
EXCHANGE_TYPE_INFORMATIONAL,
false /*isResp*/,
msgId,
new ArrayList<>(),
dummyIkePacketBytes);
}
private ReceivedIkePacket makeDeviceIdentityIkeRequest() throws Exception {
IkeNotifyPayload deviceIdentity =
new IkeNotifyPayload(
NOTIFY_TYPE_DEVICE_IDENTITY, HexDump.hexStringToByteArray("01"));
return makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
EXCHANGE_TYPE_INFORMATIONAL,
false /*isResp*/,
Arrays.asList(deviceIdentity));
}
private ReceivedIkePacket makeRoutabilityCheckIkeRequest() throws Exception {
IkeNotifyPayload cookie2Notify = new IkeNotifyPayload(NOTIFY_TYPE_COOKIE2, COOKIE2_DATA);
return makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
EXCHANGE_TYPE_INFORMATIONAL,
false /*isResp*/,
Arrays.asList(cookie2Notify));
}
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 makeRekeyIkeRequestWithPayloads(List<IkePayload> payloads)
throws Exception {
return makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
false /*isResp*/,
payloads);
}
private ReceivedIkePacket makeRekeyIkeRequest(IkeSaPayload saPayload) throws Exception {
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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 makeRekeyIkeRequestWithPayloads(payloadList);
}
private ReceivedIkePacket makeDeleteIkeRequest(IkeSaRecord saRecord) throws Exception {
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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 ArrayList<>();
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 static void assertByteArrayListEquals(
List<byte[]> expectedList, List<byte[]> resultList) {
assertEquals(expectedList.size(), resultList.size());
for (int i = 0; i < expectedList.size(); i++) {
assertArrayEquals(expectedList.get(i), resultList.get(i));
}
}
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);
doReturn(new byte[0]).when(mMockIkeMessageHelper).encode(any());
doReturn(new byte[1][0])
.when(mMockIkeMessageHelper)
.encryptAndEncode(any(), any(), any(), any(), anyBoolean(), anyInt());
}
private void resetSpyUserCbExecutor() {
reset(mSpyUserCbExecutor);
}
@Test
public void testQuit() {
mIkeSessionStateMachine.quit();
mLooper.dispatchAll();
verify(mMockCurrentIkeSocket).releaseReference(eq(mSpyIkeConnectionCtrl));
verify(mMockBusyWakelock).release();
}
@Test
public void testAllocateIkeSpi() throws Exception {
// Test randomness.
IkeSecurityParameterIndex ikeSpiOne = IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS);
IkeSecurityParameterIndex ikeSpiTwo = IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS);
assertNotEquals(ikeSpiOne.getSpi(), ikeSpiTwo.getSpi());
ikeSpiTwo.close();
// Test duplicate SPIs.
long spiValue = ikeSpiOne.getSpi();
try {
IKE_SPI_GENERATOR.allocateSpi(LOCAL_ADDRESS, spiValue);
fail("Expected to fail because duplicate SPI was assigned to the same address.");
} catch (IOException expected) {
}
ikeSpiOne.close();
IkeSecurityParameterIndex ikeSpiThree =
IKE_SPI_GENERATOR.allocateSpi(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);
mockScheduleRekey(mSpyCurrentIkeSaRecord.mSaLifetimeAlarmScheduler);
mSpyCurrentIkeSaRecord.mSaLifetimeAlarmScheduler
.scheduleLifetimeExpiryAlarm(anyString());
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);
mockScheduleRekey(rekeySaRecord.mSaLifetimeAlarmScheduler);
rekeySaRecord.mSaLifetimeAlarmScheduler.scheduleLifetimeExpiryAlarm(
anyString());
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();
}
private void setupDnsResolutionForNetwork(
Network network, int dnsLookupsForSuccess, InetAddress remoteAddress)
throws Exception {
doAnswer(new Answer() {
private int mAttempedDnsLookups = 0;
public Object answer(InvocationOnMock invocation) throws IOException {
mAttempedDnsLookups++;
if (mAttempedDnsLookups < dnsLookupsForSuccess) {
throw new UnknownHostException("DNS failed");
} else {
return new InetAddress[] {remoteAddress};
}
}
}).when(network).getAllByName(REMOTE_HOSTNAME);
}
private void setupAndVerifyDnsResolutionForIkeSession(
int dnsLookupsForSuccess, int expectedDnsLookups, boolean expectSessionClosed)
throws Exception {
mIkeSessionStateMachine.quitNow();
// Reset the network to ignore DNS resolution from mIkeSessionStateMachine creation in
// setUp()
resetDefaultNetwork();
setupDnsResolutionForNetwork(mMockDefaultNetwork, dnsLookupsForSuccess, REMOTE_ADDRESS);
IkeSessionParams ikeParams =
buildIkeSessionParamsCommon()
.setAuthPsk(mPsk)
.setServerHostname(REMOTE_HOSTNAME)
.build();
mIkeSessionStateMachine =
makeAndStartIkeSession(
ikeParams,
LOCAL_ADDRESS,
expectSessionClosed ? null : REMOTE_ADDRESS);
verify(mMockDefaultNetwork, times(expectedDnsLookups)).getAllByName(REMOTE_HOSTNAME);
if (expectSessionClosed) {
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback)
.onClosedWithException(
argThat(
e ->
e instanceof IkeInternalException
&& e.getCause() instanceof IOException));
}
}
@Test
public void testResolveRemoteHostName() throws Exception {
setupAndVerifyDnsResolutionForIkeSession(
1 /* dnsLookupsForSuccess */,
1 /* expectedDnsLookups */,
false /* expectSessionClosed */);
}
@Test
public void testResolveRemoteHostNameWithDnsRetries() throws Exception {
setupAndVerifyDnsResolutionForIkeSession(
2 /* dnsLookupsForSuccess */,
2 /* expectedDnsLookups */,
false /* expectSessionClosed */);
}
@Test
public void testResolveRemoteHostNameWithDnsFailure() throws Exception {
// Require more lookups for successful DNS than IKE allows to force failure
setupAndVerifyDnsResolutionForIkeSession(
4 /* dnsLookupsForSuccess */,
3 /* expectedDnsLookups */,
true /* expectSessionClosed */);
}
private void verifyTestMode(boolean isTestNetwork) throws Exception {
doReturn(isTestNetwork)
.when(mMockNetworkCapabilities)
.hasTransport(RandomnessFactory.TRANSPORT_TEST);
// Clear #getActiveNetwork() call in #setUp() to pass the verification in
// #makeAndStartIkeSession()
resetMockConnectManager();
Network network = mockNewNetworkAndAddress(true /*isIpv4*/);
IkeSessionParams ikeParams =
buildIkeSessionParamsCommon()
.setNetwork(network)
.setAuthPsk("psk".getBytes())
.build();
IkeSessionStateMachine ikeSession = makeAndStartIkeSession(ikeParams);
SecureRandom random = ikeSession.mIkeContext.getRandomnessFactory().getRandom();
if (isTestNetwork) {
assertNotNull(random);
assertTrue(random instanceof DeterministicSecureRandom);
} else {
assertNull(random);
}
}
@Test
public void testEnableTestMode() throws Exception {
verifyTestMode(true /* isTestNetwork */);
}
@Test
public void testDisableTestMode() throws Exception {
verifyTestMode(false /* isTestNetwork */);
}
private void verifyOutboundKePayload(int expectedDhGroup) {
verify(mMockIkeMessageHelper, atLeastOnce()).encode(mIkeMessageCaptor.capture());
IkeMessage reqMsg = mIkeMessageCaptor.getValue();
IkeKePayload kePayload =
reqMsg.getPayloadForType(IkePayload.PAYLOAD_TYPE_KE, IkeKePayload.class);
assertEquals(expectedDhGroup, kePayload.dhGroup);
}
@Test
public void testCreateIkeLocalIkeInitNegotiatesDhGroup() throws Exception {
setupFirstIkeSa();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
// Verify we started with the top proposed DH group
verifyOutboundKePayload(SaProposal.DH_GROUP_1024_BIT_MODP);
// Send back a INVALID_KE_PAYLOAD, and verify that the selected DH group changes
resetMockIkeMessageHelper();
ReceivedIkePacket resp =
makeDummyReceivedIkeInitRespPacket(
Arrays.asList(IkePayload.PAYLOAD_TYPE_NOTIFY),
Arrays.asList(INVALID_KE_PAYLOAD_HEX_STRING));
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
mLooper.dispatchAll();
verifyOutboundKePayload(SaProposal.DH_GROUP_2048_BIT_MODP);
}
private ReceivedIkePacket getIkeInitRespWithCookie() throws Exception {
IkeNotifyPayload inCookieNotify = new IkeNotifyPayload(NOTIFY_TYPE_COOKIE, COOKIE_DATA);
List<IkePayload> payloads = new ArrayList<>();
payloads.add(inCookieNotify);
return makeDummyReceivedIkeInitRespPacket(payloads);
}
@Test
public void testCreateIkeLocalIkeInitReceivesCookie() throws Exception {
setupFirstIkeSa();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
// Encode 2 times: one for mIkeInitRequestBytes and one for sending packets
verify(mMockIkeMessageHelper, times(2)).encode(mIkeMessageCaptor.capture());
IkeMessage originalReqMsg = mIkeMessageCaptor.getValue();
List<IkePayload> originalPayloadList = originalReqMsg.ikePayloadList;
// Reset to forget sending original IKE INIT request
resetMockIkeMessageHelper();
// Send back a Notify-Cookie
ReceivedIkePacket resp = getIkeInitRespWithCookie();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, resp);
mLooper.dispatchAll();
// Verify retry IKE INIT request
verify(mMockIkeMessageHelper, times(2)).encode(mIkeMessageCaptor.capture());
IkeMessage ikeInitReqMessage = mIkeMessageCaptor.getValue();
List<IkePayload> payloadList = ikeInitReqMessage.ikePayloadList;
IkeNotifyPayload outCookieNotify = (IkeNotifyPayload) payloadList.get(0);
assertEquals(NOTIFY_TYPE_COOKIE, outCookieNotify.notifyType);
assertArrayEquals(COOKIE_DATA, outCookieNotify.notifyData);
// First 4 payloads MUST follow RFC 4306 so that IKE library can be compatible with old
// implementations.
int[] expectedPayloadType =
new int[] {
PAYLOAD_TYPE_NOTIFY, PAYLOAD_TYPE_SA, PAYLOAD_TYPE_KE, PAYLOAD_TYPE_NONCE
};
int len = expectedPayloadType.length;
for (int i = 0; i < len; i++) {
assertEquals(expectedPayloadType[i], payloadList.get(i).payloadType);
}
assertEquals(originalPayloadList, payloadList.subList(1, payloadList.size()));
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeInit);
}
@Test
public void testCreateIkeLocalIkeInitRcvRespAfterRcvCookie() throws Exception {
setupFirstIkeSa();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
// Receive IKE INIT response with Cookie
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, getIkeInitRespWithCookie());
// Receive IKE INIT response
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, makeIkeInitResponse());
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
verifyIkeSaNegotiationResult();
}
@Test
public void testCreateIkeLocalIkeInitSwitchesToEncapPortsIpv4() throws Exception {
setupFirstIkeSa();
assertFalse(mSpyIkeConnectionCtrl.useUdpEncapSocket());
triggerAndVerifyIkeInitReq(true /* expectingNatDetection */, true /* expectingFakedNatd */);
receiveAndGetIkeInitResp();
assertTrue(mSpyIkeConnectionCtrl.useUdpEncapSocket());
assertEquals(NAT_DETECTED, mSpyIkeConnectionCtrl.getNatStatus());
verify(mMockIkeUdp4Socket).unregisterIke(anyLong());
}
private void restartIkeSessionWithEnforcePort4500AndVerifyIkeSocket() throws Exception {
// Quit and start a new IKE Session with IKE_OPTION_FORCE_PORT_4500
mIkeSessionStateMachine.quitNow();
IkeSessionParams ikeParams =
buildIkeSessionParamsWithIkeOptions(IKE_OPTION_FORCE_PORT_4500);
mIkeSessionStateMachine = makeAndStartIkeSession(ikeParams);
mLooper.dispatchAll();
assertTrue(mSpyIkeConnectionCtrl.useUdpEncapSocket());
}
@Test
public void testInitialStateWithEnforcePort4500() throws Exception {
restartIkeSessionWithEnforcePort4500AndVerifyIkeSocket();
}
@Test
public void testCreateIkeLocalIkeInitNatTraversalWithEnforcePort4500() throws Exception {
restartIkeSessionWithEnforcePort4500AndVerifyIkeSocket();
setupFirstIkeSa();
assertTrue(mSpyIkeConnectionCtrl.useUdpEncapSocket());
triggerAndVerifyIkeInitReq(true /* expectingNatDetection */, true /* expectingFakedNatd */);
receiveAndGetIkeInitResp();
assertTrue(mSpyIkeConnectionCtrl.useUdpEncapSocket());
assertEquals(NAT_DETECTED, mSpyIkeConnectionCtrl.getNatStatus());
}
@Test
public void testCreateIkeLocalIkeInitNatTraversalNotSupported() throws Exception {
setupFirstIkeSa();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
// Receive IKE INIT response
ReceivedIkePacket dummyReceivedIkePacket =
makeIkeInitResponseWithRequiredPayloads(
Collections.emptyList(), Collections.emptyList());
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
mLooper.dispatchAll();
// Validate socket switched
assertEquals(mMockIkeUdp4Socket, mSpyIkeConnectionCtrl.getIkeSocket());
assertEquals(NAT_TRAVERSAL_UNSUPPORTED, mSpyIkeConnectionCtrl.getNatStatus());
verify(mMockIkeUdp4Socket, never()).unregisterIke(anyLong());
}
private void verifyNatdSrcIpFromIkeInitReqMessage(IkeMessage ikeInitReqMessag) {
verifyNatdSrcIpFromIkeInitReqMessage(ikeInitReqMessag, false /* expectingFakedNatd */);
}
private void verifyNatdSrcIpFromIkeInitReqMessage(
IkeMessage ikeInitReqMessage, boolean expectingFakedNatd) {
List<IkeNotifyPayload> notifyPayloads =
ikeInitReqMessage.getPayloadListForType(
IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
IkeNotifyPayload natdSrcIpPayload = null;
for (IkeNotifyPayload notifyPayload : notifyPayloads) {
if (notifyPayload.notifyType == NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP) {
natdSrcIpPayload = notifyPayload;
}
}
assertNotNull(natdSrcIpPayload);
byte[] localNatDataNotFaked =
IkeNotifyPayload.generateNatDetectionData(
ikeInitReqMessage.ikeHeader.ikeInitiatorSpi,
ikeInitReqMessage.ikeHeader.ikeResponderSpi,
mSpyIkeConnectionCtrl.getLocalAddress(),
mSpyIkeConnectionCtrl.getLocalPort());
assertEquals(
!expectingFakedNatd,
Arrays.equals(localNatDataNotFaked, natdSrcIpPayload.notifyData));
}
private void triggerAndVerifyIkeInitReq(boolean expectingNatDetection) throws Exception {
triggerAndVerifyIkeInitReq(expectingNatDetection, false);
}
private void triggerAndVerifyIkeInitReq(
boolean expectingNatDetection, boolean expectingFakedNatd) throws Exception {
// Send IKE INIT request
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
verifyRetransmissionStarted();
// 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_IKEV2_FRAGMENTATION_SUPPORTED));
assertTrue(isNotifyExist(payloadList, NOTIFY_TYPE_SIGNATURE_HASH_ALGORITHMS));
assertEquals(
expectingNatDetection,
isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
assertEquals(
expectingNatDetection,
isNotifyExist(payloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
if (expectingNatDetection) {
verifyNatdSrcIpFromIkeInitReqMessage(ikeInitReqMessage, expectingFakedNatd);
}
}
private ReceivedIkePacket receiveAndGetIkeInitResp() throws Exception {
ReceivedIkePacket dummyReceivedIkePacket = makeIkeInitResponse();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyReceivedIkePacket);
mLooper.dispatchAll();
verifyIncrementLocaReqMsgId();
return dummyReceivedIkePacket;
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testCreateIkeLocalIkeInitWithoutIpv6NatD() throws Exception {
mIkeSessionStateMachine.quitNow();
resetMockConnectManager();
resetMockIkeMessageHelper();
// Restart mIkeSessionStateMachine so it uses IPv6 addresses
final Network v6OnlyNetwork =
mockNewNetworkAndAddress(false /* isIpv4 */, LOCAL_ADDRESS_V6, REMOTE_ADDRESS_V6);
final IkeSessionParams params =
buildIkeSessionParamsCommon()
.setAuthPsk(mPsk)
.setNetwork(v6OnlyNetwork)
.addIkeOption(IKE_OPTION_MOBIKE)
.build();
mIkeSessionStateMachine =
makeAndStartIkeSession(
params,
LOCAL_ADDRESS_V6,
REMOTE_ADDRESS_V6);
setupFirstIkeSa();
triggerAndVerifyIkeInitReq(false /* expectingNatDetection */);
receiveAndGetIkeInitResp();
assertTrue(mSpyIkeConnectionCtrl.getIkeSocket() instanceof IkeUdp6Socket);
assertEquals(NAT_TRAVERSAL_SUPPORT_NOT_CHECKED, mSpyIkeConnectionCtrl.getNatStatus());
}
@Ignore
public void disableTestCreateIkeLocalIkeInit() throws Exception {
setupFirstIkeSa();
triggerAndVerifyIkeInitReq(true /* expectingNatDetection */);
final ReceivedIkePacket dummyReceivedIkePacket = receiveAndGetIkeInitResp();
verify(mMockCurrentIkeSocket)
.registerIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()), eq(mSpyIkeConnectionCtrl));
verify(mMockIkeMessageHelper)
.decode(0, dummyReceivedIkePacket.ikeHeader, dummyReceivedIkePacket.ikePacketBytes);
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuth);
verifyRetransmissionStarted();
verifyIkeSaNegotiationResult();
}
private void verifyIkeSaNegotiationResult() throws Exception {
// 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);
assertNotNull(ikeSaRecordConfig.saLifetimeAlarmScheduler);
// Validate NAT detection
assertEquals(NAT_DETECTED, mSpyIkeConnectionCtrl.getNatStatus());
// Validate vendor IDs
List<byte[]> vendorIds = new ArrayList<>();
vendorIds.add(REMOTE_VENDOR_ID_ONE);
assertByteArrayListEquals(vendorIds, mIkeSessionStateMachine.mRemoteVendorIds);
// Validate fragmentation support negotiation
assertEquals(
Arrays.asList(EXTENSION_TYPE_FRAGMENTATION),
mIkeSessionStateMachine.mEnabledExtensions);
// Validate Signature Hash Algorithms received in IKE INIT response
Set<Short> expectedHashAlgos = new HashSet<Short>();
for (short algo : IkeAuthDigitalSignPayload.ALL_SIGNATURE_ALGO_TYPES) {
expectedHashAlgos.add(algo);
}
assertEquals(
expectedHashAlgos,
((CreateIkeLocalIkeAuth) mIkeSessionStateMachine.mCreateIkeLocalIkeAuth)
.mSetupData
.peerSignatureHashAlgorithms);
}
private void setIkeInitResults() throws Exception {
mIkeSessionStateMachine.mIkeCipher = mock(IkeCipher.class);
mIkeSessionStateMachine.mIkeIntegrity = mock(IkeMacIntegrity.class);
mIkeSessionStateMachine.mIkePrf = mock(IkeMacPrf.class);
mIkeSessionStateMachine.mSaProposal = buildNegotiatedSaProposal();
mIkeSessionStateMachine.mCurrentIkeSaRecord = mSpyCurrentIkeSaRecord;
mIkeSessionStateMachine.mRemoteVendorIds =
Arrays.asList(REMOTE_VENDOR_ID_ONE, REMOTE_VENDOR_ID_TWO);
mIkeSessionStateMachine.mEnabledExtensions.add(EXTENSION_TYPE_FRAGMENTATION);
mIkeSessionStateMachine.addIkeSaRecord(mSpyCurrentIkeSaRecord);
mSpyIkeConnectionCtrl.handleNatDetectionResultInIkeInit(
false /* isNatDetected */, mSpyCurrentIkeSaRecord.getLocalSpi());
mMockCurrentIkeSocket = mSpyIkeConnectionCtrl.getIkeSocket();
}
/** Initializes the mIkeSessionStateMachine in the IDLE state. */
private void setupIdleStateMachine() throws Exception {
verify(mMockBusyWakelock).acquire();
setIkeInitResults();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
mLooper.dispatchAll();
mDummyChildSmCallback =
createChildAndGetChildSessionSmCallback(
mMockChildSessionStateMachine, CHILD_SPI_REMOTE, mMockChildSessionCallback);
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
verify(mMockBusyWakelock).release();
// For convenience to verify wakelocks in all other places.
reset(mMockBusyWakelock);
}
private abstract class IkeAuthTestPretestBase {
protected IkeInitData mIkeInitData;
IkeAuthTestPretestBase() {
InitialSetupData initialSetupData =
new InitialSetupData(
mChildSessionParams,
mMockChildSessionCallback,
SaProposal.DH_GROUP_1024_BIT_MODP);
mIkeInitData =
new IkeInitData(
initialSetupData,
new byte[0],
new byte[0],
new IkeNoncePayload(createMockRandomFactory()),
new IkeNoncePayload(createMockRandomFactory()),
new HashSet<Short>());
}
public void mockIkeInitAndTransitionToIkeAuth() 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 inherited 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));
performStateTransition();
}
abstract void performStateTransition() throws Exception;
}
private final class IkeFirstAuthTestPretest extends IkeAuthTestPretestBase {
IkeFirstAuthTestPretest() {
super();
}
@Override
void performStateTransition() throws Exception {
mIkeSessionStateMachine.mCreateIkeLocalIkeAuth.setIkeSetupData(mIkeInitData);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mCreateIkeLocalIkeAuth);
mLooper.dispatchAll();
}
}
private abstract class IkeAuthEapTestPretestBase extends IkeAuthTestPretestBase {
protected IkeAuthData mIkeAuthData;
IkeAuthEapTestPretestBase(IkeIdPayload initIdPayload, IkeIdPayload respIdPayload)
throws Exception {
super();
List<Integer> payloadTypeList = new ArrayList<>();
List<String> payloadHexStringList = new ArrayList<>();
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> firstChildReqList =
hexStrListToIkePayloadList(
payloadTypeList, payloadHexStringList, false /*isResp*/);
mIkeAuthData =
new IkeAuthData(mIkeInitData, initIdPayload, respIdPayload, firstChildReqList);
}
}
private static IkeIdPayload buildMockIkeIdPayload() {
IkeIdPayload idPayload = mock(IkeIdPayload.class);
doReturn(new byte[0]).when(idPayload).getEncodedPayloadBody();
return idPayload;
}
private final class IkeAuthInEapTestPretest extends IkeAuthEapTestPretestBase {
IkeAuthInEapTestPretest() throws Exception {
super(buildMockIkeIdPayload(), buildMockIkeIdPayload());
}
@Override
void performStateTransition() throws Exception {
mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap.setIkeSetupData(mIkeAuthData);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap);
mLooper.dispatchAll();
}
}
private final class IkeAuthPostEapTestPretest extends IkeAuthEapTestPretestBase {
IkeAuthPostEapTestPretest(IkeIdPayload initIdPayload, IkeIdPayload respIdPayload)
throws Exception {
super(initIdPayload, respIdPayload);
}
@Override
void performStateTransition() throws Exception {
mIkeSessionStateMachine.mCreateIkeLocalIkeAuthPostEap.setIkeSetupData(mIkeAuthData);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mCreateIkeLocalIkeAuthPostEap);
mLooper.dispatchAll();
}
}
/**
* 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) {
injectChildSessionInSpyDeps(mSpyDeps, sm, callback);
mIkeSessionStateMachine.registerChildSessionCallback(
mChildSessionParams, callback, false /*isFirstChild*/);
}
private IChildSessionSmCallback verifyMakeChildAndReturnChildSmCb(
ChildSessionCallback expectedChildSessionCb) {
ArgumentCaptor<IkeContext> ikeContextCaptor = ArgumentCaptor.forClass(IkeContext.class);
verify(mSpyDeps)
.newChildSessionStateMachine(
ikeContextCaptor.capture(),
any(ChildSessionStateMachine.Config.class),
eq(expectedChildSessionCb),
mChildSessionSmCbCaptor.capture());
IkeContext ikeContext = ikeContextCaptor.getValue();
assertEquals(mLooper.getLooper(), ikeContext.getLooper());
assertEquals(mSpyContext, ikeContext.getContext());
return mChildSessionSmCbCaptor.getValue();
}
@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,
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_CHILD,
childCallback,
mChildSessionParams));
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
verify(childStateMachine)
.createChildSession(
eq(LOCAL_ADDRESS),
eq(REMOTE_ADDRESS),
any(), // udpEncapSocket
eq(mIkeSessionStateMachine.mIkePrf),
eq(mIkeSessionStateMachine.mSaProposal.getDhGroupTransforms()[0].id),
any()); // sk_d
IChildSessionSmCallback cb = verifyMakeChildAndReturnChildSmCb(childCallback);
// Mocking sending request
cb.onOutboundPayloadsReady(
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
false /*isResp*/,
new ArrayList<>(),
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,
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
mMockChildSessionCallback,
null /*childParams*/));
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
verify(mMockChildSessionStateMachine).deleteChildSession();
verify(mMockBusyWakelock).acquire();
}
@Test
public void testHandleDeleteChildBeforeCreation() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
mock(ChildSessionCallback.class),
null /*childParams*/));
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@Test
public void testTriggerRekeyChildLocal() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
mMockChildSessionCallback,
null /*childParams*/));
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
verify(mMockChildSessionStateMachine).rekeyChildSession();
verify(mMockBusyWakelock).acquire();
}
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 ArrayList<>();
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)));
verify(mMockBusyWakelock).acquire();
}
@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 testRemoteRekeyChild() throws Exception {
setupIdleStateMachine();
// Receive Rekey Create request
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);
// Send Rekey Create response
List<IkePayload> mockRekeyCreatePayloads = Arrays.asList(mock(IkePayload.class));
mDummyChildSmCallback.onOutboundPayloadsReady(
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
true /*isResp*/,
mockRekeyCreatePayloads,
mMockChildSessionStateMachine);
mLooper.dispatchAll();
IkeMessage rekeyCreateResp =
verifyAndGetOutboundEncryptedResp(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
assertEquals(mockRekeyCreatePayloads, rekeyCreateResp.ikePayloadList);
// Forget sending Rekey Create response
resetMockIkeMessageHelper();
// 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(mMockChildSessionStateMachine)
.receiveRequest(
eq(IKE_EXCHANGE_SUBTYPE_DELETE_CHILD),
eq(EXCHANGE_TYPE_INFORMATIONAL),
any(List.class));
// Send Rekey Delete response
List<IkePayload> mockRekeyDeletePayloads = Arrays.asList(mock(IkePayload.class));
mDummyChildSmCallback.onOutboundPayloadsReady(
IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
true /*isResp*/,
mockRekeyDeletePayloads,
mMockChildSessionStateMachine);
mLooper.dispatchAll();
IkeMessage rekeyDeleteResp =
verifyAndGetOutboundEncryptedResp(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
assertEquals(mockRekeyDeletePayloads, rekeyDeleteResp.ikePayloadList);
}
@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));
IkeConfigPayload configPayload =
ikeAuthReqMessage.getPayloadForType(
IkePayload.PAYLOAD_TYPE_CP, IkeConfigPayload.class);
assertNotNull(configPayload);
Map<Integer, Integer> expectedAttributes =
Map.of(
CONFIG_ATTR_IP4_PCSCF,
1,
CONFIG_ATTR_IP6_PCSCF,
1,
CONFIG_ATTR_INTERNAL_IP4_ADDRESS,
1,
CONFIG_ATTR_INTERNAL_IP6_ADDRESS,
1,
CONFIG_ATTR_APPLICATION_VERSION,
1,
CONFIG_ATTR_INTERNAL_IP4_NETMASK,
1);
Map<Integer, Integer> actualAttributes =
buildAttributeMap(configPayload.recognizedAttributeList);
assertEquals(expectedAttributes, actualAttributes);
IkeNoncePayload noncePayload =
ikeAuthReqMessage.getPayloadForType(
IkePayload.PAYLOAD_TYPE_NONCE, IkeNoncePayload.class);
assertNull(noncePayload);
return ikeAuthReqMessage;
}
private Map<Integer, Integer> buildAttributeMap(
List<IkeConfigPayload.ConfigAttribute> recognizedAttributeList) {
Map<Integer, Integer> attrCountMap = new HashMap<>();
for (IkeConfigPayload.ConfigAttribute attr : recognizedAttributeList) {
attrCountMap.compute(attr.attributeType, (key, val) -> (val == null) ? 1 : val + 1);
}
return attrCountMap;
}
private void verifyDigitalSignatureAuthentication(
IkeAuthDigitalSignPayload spyAuthPayload,
X509Certificate certificate,
IkeIdPayload respIdPayload,
List<IkePayload> authRelatedPayloads,
boolean hasChildPayloads,
boolean hasConfigPayloadInResp)
throws Exception {
IkeMessage ikeAuthReqMessage =
verifyAuthenticationCommonAndGetIkeMessage(
respIdPayload,
authRelatedPayloads,
hasChildPayloads,
hasConfigPayloadInResp,
false /* isMobikeEnabled */,
true /* isIpv4 */,
0 /* ike3gppCallbackInvocations */);
// Calling mSpyCurrentIkeSaRecord.getSkPr() inline will introduce a mockito compile error:
// InvalidUseOfMatchersException
byte[] expectedSkPr = mSpyCurrentIkeSaRecord.getSkPr();
verify(spyAuthPayload)
.verifyInboundSignature(
eq(certificate),
any(),
eq(mSpyCurrentIkeSaRecord.nonceInitiator),
eq(respIdPayload.getEncodedPayloadBody()),
eq(mIkeSessionStateMachine.mIkePrf),
eq(expectedSkPr));
assertNotNull(
ikeAuthReqMessage.getPayloadForType(
IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthDigitalSignPayload.class));
}
private IkeMessage verifySharedKeyAuthentication(
IkeAuthPskPayload spyAuthPayload,
IkeIdPayload respIdPayload,
List<IkePayload> authRelatedPayloads,
boolean hasChildPayloads,
boolean hasConfigPayloadInResp)
throws Exception {
return verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
hasChildPayloads,
hasConfigPayloadInResp,
false /* isMobikeEnabled */,
true /* isIpv4 */,
0 /* ike3gppDataListenerInvocations */);
}
private IkeMessage verifySharedKeyAuthentication(
IkeAuthPskPayload spyAuthPayload,
IkeIdPayload respIdPayload,
List<IkePayload> authRelatedPayloads,
boolean hasChildPayloads,
boolean hasConfigPayloadInResp,
boolean isMobikeEnabled,
boolean isIpv4,
int ike3gppDataListenerInvocations)
throws Exception {
IkeMessage ikeAuthReqMessage =
verifyAuthenticationCommonAndGetIkeMessage(
respIdPayload,
authRelatedPayloads,
hasChildPayloads,
hasConfigPayloadInResp,
isMobikeEnabled,
isIpv4,
ike3gppDataListenerInvocations);
// Calling mSpyCurrentIkeSaRecord.getSkPr() inline will introduce a mockito compile error:
// InvalidUseOfMatchersException
byte[] expectedSkPr = mSpyCurrentIkeSaRecord.getSkPr();
verify(spyAuthPayload)
.verifyInboundSignature(
eq(mPsk),
any(),
eq(mSpyCurrentIkeSaRecord.nonceInitiator),
eq(respIdPayload.getEncodedPayloadBody()),
eq(mIkeSessionStateMachine.mIkePrf),
eq(expectedSkPr));
assertNotNull(
ikeAuthReqMessage.getPayloadForType(
IkePayload.PAYLOAD_TYPE_AUTH, IkeAuthPskPayload.class));
return ikeAuthReqMessage;
}
private IkeMessage verifyAuthenticationCommonAndGetIkeMessage(
IkeIdPayload respIdPayload,
List<IkePayload> authRelatedPayloads,
boolean hasChildPayloads,
boolean hasConfigPayloadInResp,
boolean isMobikeEnabled,
boolean isIpv4,
int ike3gppDataListenerInvocations)
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();
}
// Validate that there is no EAP only notify payload
List<IkeNotifyPayload> notifyPayloads =
ikeAuthReqMessage.getPayloadListForType(
IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
assertFalse(hasExpectedNotifyPayload(notifyPayloads, NOTIFY_TYPE_EAP_ONLY_AUTHENTICATION));
// Validate the N1 Mode Capability payload
verifyN1ModeCapabilityPayload(notifyPayloads);
// Validate inbound IKE AUTH response
verifyIncrementLocaReqMsgId();
verifyDecodeEncryptedMessage(mSpyCurrentIkeSaRecord, authResp);
// Validate that user has been notified. Expect one invocation for
// IkeSessionCallback#onOpened and 'ike3gppDataListenerInvocations' invocations for
// Ike3gppDataListener#onIke3gppDataReceived
verify(mSpyUserCbExecutor, times(1 + ike3gppDataListenerInvocations))
.execute(any(Runnable.class));
// Verify IkeSessionConfiguration
ArgumentCaptor<IkeSessionConfiguration> ikeSessionConfigurationArgumentCaptor =
ArgumentCaptor.forClass(IkeSessionConfiguration.class);
verify(mMockIkeSessionCallback).onOpened(ikeSessionConfigurationArgumentCaptor.capture());
IkeSessionConfiguration sessionConfig = ikeSessionConfigurationArgumentCaptor.getValue();
assertNotNull(sessionConfig);
if (hasConfigPayloadInResp) {
List<InetAddress> pcscfAddressList = sessionConfig.getPcscfServers();
assertEquals(3, pcscfAddressList.size());
assertTrue(pcscfAddressList.contains(InetAddress.getByName(PCSCF_IPV6_ADDRESS1)));
assertTrue(pcscfAddressList.contains(InetAddress.getByName(PCSCF_IPV6_ADDRESS2)));
assertTrue(pcscfAddressList.contains(InetAddress.getByName(PCSCF_IPV6_ADDRESS3)));
} else {
assertTrue(sessionConfig.getPcscfServers().size() == 0);
}
assertEquals(
"" /*expected application version*/, sessionConfig.getRemoteApplicationVersion());
assertByteArrayListEquals(
Arrays.asList(REMOTE_VENDOR_ID_ONE, REMOTE_VENDOR_ID_TWO),
sessionConfig.getRemoteVendorIds());
assertTrue(
sessionConfig.isIkeExtensionEnabled(
IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION));
assertEquals(
isMobikeEnabled,
sessionConfig.isIkeExtensionEnabled(IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE));
IkeSessionConnectionInfo ikeConnInfo = sessionConfig.getIkeSessionConnectionInfo();
InetAddress expectedLocalAddress = isIpv4 ? LOCAL_ADDRESS : LOCAL_ADDRESS_V6;
InetAddress expectedRemoteAddress = isIpv4 ? REMOTE_ADDRESS : REMOTE_ADDRESS_V6;
assertEquals(expectedLocalAddress, ikeConnInfo.getLocalAddress());
assertEquals(expectedRemoteAddress, ikeConnInfo.getRemoteAddress());
assertEquals(mMockDefaultNetwork, ikeConnInfo.getNetwork());
// 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(expectedLocalAddress),
eq(expectedRemoteAddress),
any(), // udpEncapSocket
eq(mIkeSessionStateMachine.mIkePrf),
eq(mIkeSessionStateMachine.mSaProposal.getDhGroupTransforms()[0].id),
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));
if (hasConfigPayloadInResp) {
assertTrue(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_CP));
} else {
assertFalse(isIkePayloadExist(childRespList, IkePayload.PAYLOAD_TYPE_CP));
}
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);
IChildSessionSmCallback cb = verifyMakeChildAndReturnChildSmCb(mMockChildSessionCallback);
cb.onProcedureFinished(mMockChildSessionStateMachine);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
return ikeAuthReqMessage;
}
private void verifyN1ModeCapabilityPayload(List<IkeNotifyPayload> notifyPayloads)
throws Exception {
IkeNotifyPayload n1ModeCapabilityPayload = null;
for (IkeNotifyPayload notifyPayload : notifyPayloads) {
if (notifyPayload.notifyType == NOTIFY_TYPE_N1_MODE_CAPABILITY) {
n1ModeCapabilityPayload = notifyPayload;
}
}
// Only expect a N1_MODE_CAPABILITY payload if an Ike3gppExtension and PDU Session ID
// are specified.
Ike3gppExtension ike3gppExtension =
mIkeSessionStateMachine.mIkeSessionParams.getIke3gppExtension();
if (ike3gppExtension == null || !ike3gppExtension.getIke3gppParams().hasPduSessionId()) {
assertNull(n1ModeCapabilityPayload);
} else {
byte[] expectedNotifyData =
TestUtils.hexStringToByteArray(N1_MODE_CAPABILITY_PAYLOAD_DATA);
assertArrayEquals(expectedNotifyData, n1ModeCapabilityPayload.notifyData);
}
}
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() {
return makeRespIdPayload(REMOTE_ID_FQDN);
}
private IkeNotifyPayload makeDeviceIdentityPayloadFromNetwork() {
return new IkeNotifyPayload(
NOTIFY_TYPE_DEVICE_IDENTITY, HexDump.hexStringToByteArray("01"));
}
private IkeIdPayload makeRespIdPayload(IkeIdentification ikeId) {
return new IkeIdPayload(false /* isInitiator */, ikeId);
}
private void verifyEmptyInformationalSent(int count, boolean expectedResp) {
verify(mMockIkeMessageHelper, times(count))
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
argThat(
msg -> {
return msg.ikePayloadList.isEmpty()
&& msg.ikeHeader.isResponseMsg == expectedResp
&& msg.ikeHeader.fromIkeInitiator
&& msg.ikeHeader.exchangeType
== IkeHeader.EXCHANGE_TYPE_INFORMATIONAL;
}),
anyBoolean(),
anyInt());
}
@Test
public void testCreateIkeLocalIkeAuthDefersOtherMessages() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with Auth-PSK, ID-Responder and config payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
ReceivedIkePacket req =
makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
false,
Collections.emptyList(),
Collections.emptyList());
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, req);
verifyEmptyInformationalSent(0, true /* expcetedResp*/);
// Send IKE AUTH response to IKE state machine to trigger moving to next state
IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
authRelatedPayloads.add(makeConfigPayload());
ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
mLooper.dispatchAll();
verifyEmptyInformationalSent(1, true /* expcetedResp*/);
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
}
@Test
public void testCreateIkeLocalIkeAuthDigitalSignature() throws Exception {
// Quit and restart IKE Session with Digital Signature Auth params
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsDigitalSignature());
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
// Build IKE AUTH response with Digital Signature Auth, ID-Responder and config payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthDigitalSignPayload spyAuthPayload = makeSpyDigitalSignAuthPayload();
authRelatedPayloads.add(spyAuthPayload);
authRelatedPayloads.add(new IkeCertX509CertPayload(mServerEndCertificate));
IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
authRelatedPayloads.add(makeConfigPayload());
verifyDigitalSignatureAuthentication(
spyAuthPayload,
mServerEndCertificate,
respIdPayload,
authRelatedPayloads,
true /*hasChildPayloads*/,
true /*hasConfigPayloadInResp*/);
verifyRetransmissionStopped();
}
@Test
public void testCreateIkeLocalIkeAuthPsk() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with Auth-PSK, ID-Responder and config payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
authRelatedPayloads.add(makeConfigPayload());
verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
true /*hasChildPayloads*/,
true /*hasConfigPayloadInResp*/);
verifyRetransmissionStopped();
}
@Test
public void testCreateIkeLocalIkeAuthDigitalSignatureIdMismatch() throws Exception {
// Quit and restart IKE Session with Digital Signature Auth params
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsDigitalSignature());
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
resetMockIkeMessageHelper();
// Build IKE AUTH response with Digital Signature Auth, ID-Responder and config payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthDigitalSignPayload spyAuthPayload = makeSpyDigitalSignAuthPayload();
authRelatedPayloads.add(spyAuthPayload);
authRelatedPayloads.add(new IkeCertX509CertPayload(mServerEndCertificate));
authRelatedPayloads.add(makeRespIdPayload(REMOTE_ID_IPV4));
sendAuthFailRespAndVerifyCloseIke(makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
}
@Test
public void testCreateIkeLocalIkeAuthPskVerifyFail() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
resetMockIkeMessageHelper();
// Build IKE AUTH response with invalid Auth-PSK Payload and ID-Responder Payload.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
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);
sendAuthFailRespAndVerifyCloseIke(makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
}
private void sendAuthFailRespAndVerifyCloseIke(ReceivedIkePacket authFailResp)
throws Exception {
// Send response to IKE state machine
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authFailResp);
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)
.onClosedWithException(any(AuthenticationFailedException.class));
}
@Test
public void testAuthHandlesIkeErrorNotify() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
resetMockIkeMessageHelper();
// Mock rejecting IKE AUTH with Authentication Failure notification
ReceivedIkePacket mockAuthFailPacket =
makeIkeAuthRespWithoutChildPayloads(
Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_AUTHENTICATION_FAILED)));
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
mLooper.dispatchAll();
// Verify IKE Session is closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback)
.onClosedWithException(any(AuthenticationFailedException.class));
}
@Test
public void testAuthHandlesCreateChildErrorNotify() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
resetMockIkeMessageHelper();
// Mock rejecting IKE AUTH with a Create Child error notification
ReceivedIkePacket mockAuthFailPacket =
makeIkeAuthRespWithoutChildPayloads(
Arrays.asList(new IkeNotifyPayload(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE)));
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockAuthFailPacket);
mLooper.dispatchAll();
// Verify IKE Session is closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
ArgumentCaptor<IkeProtocolException> captor =
ArgumentCaptor.forClass(IkeProtocolException.class);
verify(mMockIkeSessionCallback).onClosedWithException(captor.capture());
IkeProtocolException exception = captor.getValue();
assertEquals(ERROR_TYPE_INTERNAL_ADDRESS_FAILURE, exception.getErrorType());
}
@Test
public void testAuthPskHandleRespWithParsingError() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
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).onClosedWithException(any(InvalidSyntaxException.class));
}
@Test
public void testAuthWithOptionAcceptAnyRemoteId() throws Exception {
mIkeSessionStateMachine.quitNow();
IkeSessionParams ikeSessionParams =
buildIkeSessionParamsWithIkeOptions(
IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID);
mIkeSessionStateMachine = makeAndStartIkeSession(ikeSessionParams);
// Mock IKE INIT
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload that is different
// from configured ID-Responder.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
IkeIdPayload respIdPayload = makeRespIdPayload(REMOTE_ID_IPV4);
authRelatedPayloads.add(respIdPayload);
// Send response to IKE state machine and verify authentication is done.
verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
true /*hasChildPayloads*/,
false /*hasConfigPayloadInResp*/);
verifyRetransmissionStopped();
}
@Test
public void testAuthRejectOtherResponderId() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload that is different
// from configured ID-Responder.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
IkeIdPayload respIdPayload = makeRespIdPayload(REMOTE_ID_IPV4);
authRelatedPayloads.add(respIdPayload);
// Send response to IKE state machine
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeIkeAuthRespWithChildPayloads(authRelatedPayloads));
mLooper.dispatchAll();
// Verify IKE Session is closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback)
.onClosedWithException(any(AuthenticationFailedException.class));
}
@Test
public void testCreateIkeLocalIkeAuthPreEap() throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsEap());
// Mock IKE INIT
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with EAP. Auth, ID-Resp and Cert payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
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();
IkeAuthData setupData =
((CreateIkeLocalIkeAuthInEap) mIkeSessionStateMachine.mCreateIkeLocalIkeAuthInEap)
.mSetupData;
assertNotNull(setupData.initIdPayload);
assertNotNull(setupData.respIdPayload);
}
private IEapCallback verifyEapAuthenticatorCreatedAndGetCallback(
EapSessionConfig eapSessionConfig) {
ArgumentCaptor<IkeContext> ikeContextCaptor = ArgumentCaptor.forClass(IkeContext.class);
ArgumentCaptor<IEapCallback> eapCbCaptor = ArgumentCaptor.forClass(IEapCallback.class);
verify(mSpyDeps)
.newEapAuthenticator(
ikeContextCaptor.capture(), eapCbCaptor.capture(), eq(eapSessionConfig));
IkeContext ikeContext = ikeContextCaptor.getValue();
assertEquals(mIkeSessionStateMachine.getHandler().getLooper(), ikeContext.getLooper());
assertEquals(mSpyContext, ikeContext.getContext());
return eapCbCaptor.getValue();
}
@Test
public void testCreateIkeLocalIkeAuthInEapStartsAuthenticatorAndProxiesMessage()
throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EAP_START_EAP_AUTH, new IkeEapPayload(EAP_DUMMY_MSG));
mLooper.dispatchAll();
verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
verify(mMockEapAuthenticator).processEapMessage(eq(EAP_DUMMY_MSG));
}
@Test
public void testInformationalResponseWithDeviceIdentity() throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine =
makeAndStartIkeSession(buildIkeSessionParamsEapAkaWithDeviceIdentity());
setIkeInitResults();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
verifyRcvDeviceIdentityCheckReqAndReply();
}
@Test
public void testCreateIkeLocalIkeAuthInEapOutboundResponseIncludesDeviceIdentity()
throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine =
makeAndStartIkeSession(buildIkeSessionParamsEapAkaWithDeviceIdentity());
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
verifyRetransmissionStarted();
// Build IKE AUTH response with EAP. Auth, ID-Resp and device identity
List<IkePayload> authRelatedPayloads = new ArrayList<>();
authRelatedPayloads.add(new IkeEapPayload(EAP_DUMMY_MSG));
authRelatedPayloads.add(makeRespIdPayload());
authRelatedPayloads.add(makeDeviceIdentityPayloadFromNetwork());
// Send IKE AUTH response to IKE state machine
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeIkeAuthRespWithoutChildPayloads(authRelatedPayloads));
mLooper.dispatchAll();
IEapCallback callback =
verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfigEapAka);
callback.onResponse(
EAP_DUMMY_MSG, EAP_RESPONSE_FLAG_MASK_WITH_EAP_AKA_SERVER_AUTHENTICATED);
mLooper.dispatchAll();
verifyRetransmissionStarted();
verify(mMockIkeMessageHelper, times(2))
.encryptAndEncode(
anyObject(),
anyObject(),
eq(mSpyCurrentIkeSaRecord),
mIkeMessageCaptor.capture(),
anyBoolean(),
anyInt());
IkeMessage ikeAuthReqMessage = mIkeMessageCaptor.getValue();
// Validate the DEVICE_IDENTITY payload and the content sent to the network
List<IkeNotifyPayload> notifyPayloads =
ikeAuthReqMessage.getPayloadListForType(
IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
IkeNotifyPayload deviceIdentityPayload = null;
for (IkeNotifyPayload notifyPayload : notifyPayloads) {
if (notifyPayload.notifyType == NOTIFY_TYPE_DEVICE_IDENTITY) {
deviceIdentityPayload = notifyPayload;
}
}
assertArrayEquals(DEVICE_IDENTITY_PAYLOAD_IMEI, deviceIdentityPayload.notifyData);
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.CreateIkeLocalIkeAuthInEap);
}
@Test
public void testCreateIkeLocalIkeAuthInEapHandlesOutboundResponse() throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
callback.onResponse(EAP_DUMMY_MSG, EAP_RESPONSE_FLAGS_NOT_SET);
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(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
// Mock sending IKE_AUTH{EAP} request
IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
callback.onResponse(EAP_DUMMY_MSG, EAP_RESPONSE_FLAGS_NOT_SET);
mLooper.dispatchAll();
verifyRetransmissionStarted();
// Send IKE AUTH response with no EAP Payload to IKE state machine
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET,
makeIkeAuthRespWithoutChildPayloads(new ArrayList<>()));
mLooper.dispatchAll();
// Verify state machine quit properly
verify(mMockIkeSessionCallback)
.onClosedWithException(any(AuthenticationFailedException.class));
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
public void testCreateIkeLocalIkeAuthInEapHandlesSuccess() throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
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(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
Throwable error = new IllegalArgumentException();
callback.onError(error);
mLooper.dispatchAll();
// Fires user error callbacks
verify(mMockIkeSessionCallback)
.onClosedWithException(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(buildIkeSessionParamsEap());
// Setup state and go to IN_EAP state
new IkeAuthInEapTestPretest().mockIkeInitAndTransitionToIkeAuth();
mLooper.dispatchAll();
IEapCallback callback = verifyEapAuthenticatorCreatedAndGetCallback(mEapSessionConfig);
callback.onFail();
mLooper.dispatchAll();
// Fires user error callbacks
verify(mMockIkeSessionCallback)
.onClosedWithException(any(AuthenticationFailedException.class));
// Verify state machine quit properly
verify(mSpyCurrentIkeSaRecord).close();
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
public void testCreateIkeLocalIkeAuthPostEap() throws Exception {
verifyCreateIkeLocalIkeAuthPostEap(
buildIkeSessionParamsEap(),
new ArrayList<>() /* authRelatedPayloads */,
false /* isMobikeEnabled */);
}
private void verifyCreateIkeLocalIkeAuthPostEap(
IkeSessionParams params, List<IkePayload> authRelatedPayloads, boolean isMobikeEnabled)
throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(params);
// Setup state and go to IN_EAP state
IkeIdPayload initIdPayload = mock(IkeIdPayload.class);
doReturn(new byte[0]).when(initIdPayload).getEncodedPayloadBody();
IkeIdPayload respIdPayload = makeRespIdPayload();
new IkeAuthPostEapTestPretest(initIdPayload, respIdPayload)
.mockIkeInitAndTransitionToIkeAuth();
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_EAP_FINISH_EAP_AUTH, mPsk);
mLooper.dispatchAll();
verifyRetransmissionStarted();
// Build IKE AUTH response with Auth-PSK Payload and ID-Responder Payload.
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
false /*hasChildPayloads*/,
false /*hasConfigPayloadInResp*/,
isMobikeEnabled,
true /* isIpv4 */,
0 /* ike3gppDataListenerInvocations */);
verifyRetransmissionStopped();
}
@Test
public void testCreateIkeLocalIkeAuthHandlesFirstFrag() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
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 {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
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 ArrayList<>();
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 {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
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).onClosedWithException(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,
mLocalRequestFactory.getIkeLocalRequest(
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();
verifyRekeyIkeLocalCreateHandlesResponse();
}
private void verifyRekeyIkeLocalCreateHandlesResponse() throws Exception {
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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(mSpyIkeConnectionCtrl.getIkeSocket())
.registerIke(eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mSpyIkeConnectionCtrl));
}
@Test
public void testRekeyIkeLocalCreateHandleRespWithParsingError() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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).onClosedWithException(any(InvalidSyntaxException.class));
}
@Test
public void testRekeyIkeLocalCreateHandleRespWithNonFatalErrorNotify() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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 and retry is scheduled
verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@Test
public void testRekeyIkeLocalCreateHandleRespWithFatalErrorNotify() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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).onClosedWithException(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,
mLocalRequestFactory.getIkeLocalRequest(
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).onClosedWithException(any(IkeInternalException.class));
}
@Test
public void testRekeyIkeLocalCreateHandleReqWithNonFatalError() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
verifyRetransmissionStarted();
resetMockIkeMessageHelper();
// Build protocol exception
List<Integer> unsupportedPayloads = new ArrayList<>();
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 mockRescheduleRekey(IkeSaRecord spySaRecord) {
IkeLocalRequest rekeyReq =
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE);
doAnswer(
(invocation) -> {
mIkeSessionStateMachine.sendMessageDelayed(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE,
rekeyReq,
RETRY_INTERVAL_MS);
return null;
})
.when(spySaRecord)
.rescheduleRekey(eq(RETRY_INTERVAL_MS));
}
@Test
public void testRekeyIkeLocalCreateHandleRespWithTempFailure() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
// Mock sending TEMPORARY_FAILURE response
mockRcvTempFail();
mLooper.dispatchAll();
verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
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(mMockCurrentIkeSocket).unregisterIke(eq(mSpyCurrentIkeSaRecord.getLocalSpi()));
verify(mMockCurrentIkeSocket, 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();
}
@Ignore
public void disableTestRekeyIkeLocalDeleteHandlesRespWithParsingError() 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 =
verifyAndGetOutboundEncryptedResp(IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA);
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(mMockCurrentIkeSocket)
.registerIke(
eq(mSpyRemoteInitIkeSaRecord.getLocalSpi()), eq(mSpyIkeConnectionCtrl));
}
@Ignore
public void disableTestRekeyIkeRemoteCreateHandlesInvalidReq() 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);
}
@Ignore
public void disableTestRekeyIkeRemoteCreateSaCreationFailure() 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);
}
@Test
public void testRekeyIkeRemoteCreateHandlesInvalidKePayload() throws Exception {
setupIdleStateMachine();
// Build Rekey request
// SA Payload: ENCR_AES_CBC(128)|AUTH_HMAC_SHA1_96|DH_1024_BIT_MODP|PRF_HMAC_SHA1
IkePayload saPayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_SA,
false /*isResp*/,
IKE_REKEY_SA_PAYLOAD_HEX_STRING);
// Unrecognized DH Group: 0x0fff
String unrecognizedKePayload =
"280000880fff0000b4a2faf4bb54878ae21d638512ece55d9236fc50"
+ "46ab6cef82220f421f3ce6361faf36564ecb6d28798a94aa"
+ "d7b2b4b603ddeaaa5630adb9ece8ac37534036040610ebdd"
+ "92f46bef84f0be7db860351843858f8acf87056e272377f7"
+ "0c9f2d81e29c7b0ce4f291a3a72476bb0b278fd4b7b0a4c2"
+ "6bbeb08214c7071376079587";
IkePayload kePayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_KE, false /*isResp*/, unrecognizedKePayload);
IkePayload noncePayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_NONCE,
false /*isResp*/,
NONCE_INIT_PAYLOAD_HEX_STRING);
ReceivedIkePacket request =
makeDummyEncryptedReceivedIkePacketWithPayloadList(
mSpyCurrentIkeSaRecord,
IkeHeader.EXCHANGE_TYPE_CREATE_CHILD_SA,
false /*isResp*/,
Arrays.asList(saPayload, kePayload, noncePayload));
// Receive Rekey request
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
verifyProcessRekeyReqFailure(ERROR_TYPE_INVALID_KE_PAYLOAD);
}
@Test
public void testRejectRemoteRekeyWithoutDhGroupInProposal() throws Exception {
setupIdleStateMachine();
// Build a Rekey request that does not propose DH groups.
String rekeySaPayloadWithoutDhGroup =
"22000038000000340101080400000000000000FF0300000c0100000c800e0080030"
+ "000080300000203000008020000020000000802000002";
IkePayload saPayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_SA, false /*isResp*/, rekeySaPayloadWithoutDhGroup);
IkePayload kePayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_KE, false /*isResp*/, KE_PAYLOAD_HEX_STRING);
IkePayload noncePayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_NONCE,
false /*isResp*/,
NONCE_INIT_PAYLOAD_HEX_STRING);
ReceivedIkePacket request =
makeRekeyIkeRequestWithPayloads(Arrays.asList(saPayload, kePayload, noncePayload));
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
verifyProcessRekeyReqFailure(ERROR_TYPE_NO_PROPOSAL_CHOSEN);
}
@Test
public void testRejectRemoteRekeyWithoutKePayload() throws Exception {
setupIdleStateMachine();
// Build a Rekey request that proposes DH groups but does not include a KE payload
IkePayload saPayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_SA,
false /*isResp*/,
IKE_REKEY_SA_PAYLOAD_HEX_STRING);
IkePayload noncePayload =
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_NONCE,
false /*isResp*/,
NONCE_INIT_PAYLOAD_HEX_STRING);
ReceivedIkePacket request =
makeRekeyIkeRequestWithPayloads(Arrays.asList(saPayload, noncePayload));
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
verifyProcessRekeyReqFailure(ERROR_TYPE_INVALID_SYNTAX);
}
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);
}
@Ignore
public void disableTestSimulRekey() throws Exception {
setupIdleStateMachine();
// Prepare "rekeyed" SA
setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
doReturn(1).when(mSpyLocalInitIkeSaRecord).compareTo(mSpyRemoteInitIkeSaRecord);
// Send Rekey request on mSpyCurrentIkeSaRecord
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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(mMockCurrentIkeSocket)
.registerIke(eq(mSpyLocalInitIkeSaRecord.getLocalSpi()), eq(mSpyIkeConnectionCtrl));
// 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(
mIkeSessionStateMachine.mIkeSessionParams.getSoftLifetimeMsInternal());
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,
mLocalRequestFactory.getIkeLocalRequest(
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(
mIkeSessionStateMachine.mIkeSessionParams.getSoftLifetimeMsInternal());
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),
mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1);
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 ArrayList<>()));
// 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(mMockCurrentIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
verify(mMockCurrentIkeSocket).sendIkePacket(eq(dummyReqBytesList[1]), eq(REMOTE_ADDRESS));
verifyLastSentRespAllPackets(dummyLastRespBytes, mSpyCurrentIkeSaRecord);
}
@Test
public void testRetransmittedPacketsAreIdentical() throws Exception {
setupIdleStateMachine();
IkeMessage mockIkeReqMsg = mock(IkeMessage.class);
byte[][] dummyReqBytesList =
new byte[][] {"testRetransmittedPacketsAreIdentical".getBytes()};
doReturn(dummyReqBytesList)
.when(mockIkeReqMsg)
.encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
IkeSessionStateMachine.EncryptedRetransmitter retransmitter =
mIkeSessionStateMachine.new EncryptedRetransmitter(mockIkeReqMsg);
// Packet is immediately sent out
verify(mMockCurrentIkeSocket).sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
verify(mockIkeReqMsg)
.encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
// Retransmit packet
retransmitter.retransmit();
verify(mMockCurrentIkeSocket, times(2))
.sendIkePacket(eq(dummyReqBytesList[0]), eq(REMOTE_ADDRESS));
verify(mockIkeReqMsg)
.encryptAndEncode(any(), any(), eq(mSpyCurrentIkeSaRecord), anyBoolean(), anyInt());
}
// 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*/, mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1);
byte[] dummyIkeReqFirstPacket = "testLastSentRequest".getBytes();
byte[][] dummyIkeResp =
new byte[][] {
"testLastSentRespFrag1".getBytes(), "testLastSentRespFrag2".getBytes()
};
doReturn(dummyIkeResp)
.when(mMockIkeMessageHelper)
.encryptAndEncode(
any(),
any(),
eq(mSpyCurrentIkeSaRecord),
any(IkeMessage.class),
anyBoolean(),
anyInt());
// 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(mMockCurrentIkeSocket).sendIkePacket(eq(dummyIkeResp[0]), eq(REMOTE_ADDRESS));
verify(mMockCurrentIkeSocket).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.getRemoteRequestMessageId() - 1);
// 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(mMockCurrentIkeSocket).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.getRemoteRequestMessageId() - 1);
// 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(mMockCurrentIkeSocket, never()).sendIkePacket(any(), any());
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@Test
public void testRcvRetransmittedRequestBeforeReplyOriginalRequest() throws Exception {
setupIdleStateMachine();
// Mock last sent response
byte[][] dummyIkeResp = new byte[][] {"testLastSentResponse".getBytes()};
mSpyCurrentIkeSaRecord.updateLastSentRespAllPackets(
Arrays.asList(dummyIkeResp),
mSpyCurrentIkeSaRecord.getRemoteRequestMessageId() - 1);
// Send request with next message ID
IkeDeletePayload[] inboundDelPayloads =
new IkeDeletePayload[] {new IkeDeletePayload(new int[] {CHILD_SPI_REMOTE})};
ReceivedIkePacket request = makeDeleteChildPacket(inboundDelPayloads, false /*isResp*/);
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
// Verify that no response has been sent out since we didn't configure Child Session to
// respond
verify(mMockCurrentIkeSocket, never()).sendIkePacket(any(), any());
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
// Retransmit the request
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, request);
mLooper.dispatchAll();
// Verify that no response has been sent out and state machine is still in
// ChildProcedureOngoing
verify(mMockCurrentIkeSocket, never()).sendIkePacket(any(), any());
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
}
@Test
public void testDiscardRetransmittedResponse() throws Exception {
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
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 ArrayList<>(),
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,
mLocalRequestFactory.getIkeLocalRequest(
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,
mLocalRequestFactory.getIkeLocalRequest(
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,
mLocalRequestFactory.getIkeLocalRequest(
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).onClosedWithException(any(InvalidSyntaxException.class));
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
public void testDeleteIkeLocalDeleteHandlesInvalidResp() throws Exception {
setupIdleStateMachine();
// Send delete request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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).onClosedWithException(any(InvalidSyntaxException.class));
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
public void testDeleteIkeLocalDeleteReceivedNonDeleteRequest() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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();
verifySendTempFailResponse();
}
private void verifySendTempFailResponse() {
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();
verifyIkeDeleteRequestHandled();
}
private void verifyIkeDeleteRequestHandled() throws Exception {
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 testKillSessionDeleteIkeRequestSent() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.killSession();
mLooper.dispatchAll();
verify(mSpyCurrentIkeSaRecord).close();
verify(mMockCurrentIkeSocket).unregisterIke(mSpyCurrentIkeSaRecord.getInitiatorSpi());
verifyNotifyUserCloseSession();
// Verify outbound request
IkeMessage req = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = req.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL, ikeHeader.exchangeType);
assertFalse(ikeHeader.isResponseMsg);
assertEquals(1, req.ikePayloadList.size());
assertEquals(IkePayload.PAYLOAD_TYPE_DELETE, req.ikePayloadList.get(0).payloadType);
// Verify state machine quit properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockBusyWakelock).release();
}
@Test
public void testKillSessionNoDeleteIkeRequestSent() throws Exception {
setupIdleStateMachine();
// Transition to state that does not send IKE delete requests
InitialSetupData initialSetupData =
new InitialSetupData(
mChildSessionParams,
mMockChildSessionCallback,
SaProposal.DH_GROUP_1024_BIT_MODP);
mIkeSessionStateMachine.mCreateIkeLocalIkeInit.setIkeSetupData(initialSetupData);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mCreateIkeLocalIkeInit);
mLooper.dispatchAll();
mIkeSessionStateMachine.killSession();
mLooper.dispatchAll();
verify(mSpyCurrentIkeSaRecord).close();
verify(mMockCurrentIkeSocket).unregisterIke(mSpyCurrentIkeSaRecord.getInitiatorSpi());
verifyNotifyUserCloseSession();
// Verify no outbound request
verifyEncryptAndEncodeNeverCalled();
// Verify state machine quit properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockBusyWakelock).release();
}
private IkeMessage verifyAndGetOutboundEncryptedResp(int exchangeType) {
IkeMessage resp = verifyEncryptAndEncodeAndGetMessage(mSpyCurrentIkeSaRecord);
IkeHeader ikeHeader = resp.ikeHeader;
assertEquals(IkePayload.PAYLOAD_TYPE_SK, ikeHeader.nextPayloadType);
assertEquals(exchangeType, ikeHeader.exchangeType);
assertTrue(ikeHeader.isResponseMsg);
assertEquals(mSpyCurrentIkeSaRecord.isLocalInit, ikeHeader.fromIkeInitiator);
return resp;
}
private IkeMessage verifyAndGetOutboundInformationalResp() {
return verifyAndGetOutboundEncryptedResp(IkeHeader.EXCHANGE_TYPE_INFORMATIONAL);
}
@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 = verifyAndGetOutboundInformationalResp();
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 = verifyAndGetOutboundInformationalResp();
assertTrue(resp.ikePayloadList.isEmpty());
}
private void executeAndVerifySendLocalDPD() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mDpdIkeLocalInfo);
mLooper.dispatchAll();
verifyEmptyInformationalSent(1, false /* expectedResp*/);
resetMockIkeMessageHelper();
}
@Test
public void testDpdIkeLocalInfoRcvDpdReq() throws Exception {
executeAndVerifySendLocalDPD();
mIkeSessionStateMachine.sendMessage(
CMD_RECEIVE_IKE_PACKET, makeDpdIkeRequest(mSpyCurrentIkeSaRecord));
mLooper.dispatchAll();
verifyEmptyInformationalSent(1, true /* expectedResp*/);
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.DpdIkeLocalInfo);
}
@Test
public void testDpdIkeLocalInfoRcvDeleteIkeReq() throws Exception {
executeAndVerifySendLocalDPD();
mIkeSessionStateMachine.sendMessage(
CMD_RECEIVE_IKE_PACKET, makeDeleteIkeRequest(mSpyCurrentIkeSaRecord));
mLooper.dispatchAll();
verifyEmptyInformationalSent(1, true /* expectedResp*/);
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
public void testDpdIkeLocalInfoRcvRekeyIkeReq() throws Exception {
executeAndVerifySendLocalDPD();
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, makeRekeyIkeRequest());
mLooper.dispatchAll();
verifySendTempFailResponse();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.DpdIkeLocalInfo);
}
@Test
public void testIdleTriggersNewRequests() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
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,
mLocalRequestFactory.getIkeLocalRequest(
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(mChildSessionParams, null);
fail("Expected to fail due to missing ChildSessionCallback");
} catch (IllegalArgumentException expected) {
}
// Expect failure - callbacks already registered
try {
mIkeSessionStateMachine.openChildSession(
mChildSessionParams, mMockChildSessionCallback);
fail("Expected to fail due to invalid ChildSessionCallback");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testOpenChildSession() throws Exception {
setupIdleStateMachine();
ChildSessionCallback cb = mock(ChildSessionCallback.class);
mIkeSessionStateMachine.openChildSession(mChildSessionParams, cb);
// Test that inserting the same cb returns an error, even before the state
// machine has a chance to process it.
try {
mIkeSessionStateMachine.openChildSession(mChildSessionParams, cb);
fail("Expected to fail due to invalid ChildSessionCallback");
} catch (IllegalArgumentException expected) {
}
verifyMakeChildAndReturnChildSmCb(cb);
// Verify state in IkeSessionStateMachine
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
synchronized (mIkeSessionStateMachine.mChildCbToSessions) {
assertTrue(mIkeSessionStateMachine.mChildCbToSessions.containsKey(cb));
}
}
@Test(expected = IllegalArgumentException.class)
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testOpenChildSessionWithMobikeAndTransport() throws Exception {
mIkeSessionStateMachine = restartStateMachineWithMobikeConfigured();
mIkeSessionStateMachine.openChildSession(
mock(TransportModeChildSessionParams.class), mock(ChildSessionCallback.class));
}
@Test
public void testCloseChildSessionValidatesArgs() throws Exception {
setupIdleStateMachine();
// Expect failure - callbacks not registered
try {
mIkeSessionStateMachine.closeChildSession(mock(ChildSessionCallback.class));
fail("Expected to fail since callback is not registered");
} 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(mChildSessionParams, 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);
IkeSessionParams mockSessionParams = mock(IkeSessionParams.class);
when(mockSessionParams.getServerHostname()).thenReturn(REMOTE_HOSTNAME);
RuntimeException cause = new RuntimeException();
when(mockSessionParams.getSaProposalsInternal()).thenThrow(cause);
DhGroupTransform dhGroupTransform = new DhGroupTransform(SaProposal.DH_GROUP_2048_BIT_MODP);
IkeSaProposal mockSaProposal = mock(IkeSaProposal.class);
when(mockSaProposal.getDhGroupTransforms())
.thenReturn(new DhGroupTransform[] {dhGroupTransform});
when(mockSessionParams.getSaProposals()).thenReturn(Arrays.asList(mockSaProposal));
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(mockSessionParams);
// Send IKE INIT request
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_LOCAL_REQUEST_CREATE_IKE);
mLooper.dispatchAll();
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mSpyUserCbExecutor).execute(any(Runnable.class));
verify(spyIkeLog).wtf(anyString(), anyString(), any(RuntimeException.class));
ArgumentCaptor<IkeInternalException> internalExceptionCaptor =
ArgumentCaptor.forClass(IkeInternalException.class);
verify(mMockIkeSessionCallback).onClosedWithException(internalExceptionCaptor.capture());
IkeInternalException internalException = internalExceptionCaptor.getValue();
// Verify that the Exception which caused the IkeSessionStateMachine to close is the same
// one mocked for IkeSessionParams#getSaProposalsInternal
assertSame(cause, internalException.getCause());
}
@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).onClosedWithException(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 ArrayList<>();
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)
.onClosedWithException(
argThat(err -> err instanceof NoValidProposalChosenException));
// Verify state machine quit properly
assertNull(mIkeSessionStateMachine.getCurrentState());
}
private void mockSendRekeyChildReq() throws Exception {
setupIdleStateMachine();
ChildLocalRequest childLocalRequest =
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_CHILD,
mMockChildSessionCallback,
null /*childParams*/);
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 ArrayList<>(),
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();
}
@Ignore
public void disableTestTempFailureHandlerTimeout() 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).onClosedWithException(any(IkeInternalException.class));
}
@Test
public void testTempFailureHandlerCancelTimer() throws Exception {
mockRescheduleRekey(mSpyCurrentIkeSaRecord);
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
verifyRetransmissionStarted();
// Mock sending TEMPORARY_FAILURE response
mockRcvTempFail();
mLooper.dispatchAll();
verify(mSpyCurrentIkeSaRecord).rescheduleRekey(eq(RETRY_INTERVAL_MS));
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);
// Prepare "rekeyed" SA
setupRekeyedIkeSa(mSpyLocalInitIkeSaRecord);
// Receive valid Rekey-Create response
ReceivedIkePacket dummyRekeyIkeRespReceivedPacket = makeRekeyIkeResponse();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRekeyIkeRespReceivedPacket);
mLooper.dispatchAll();
// Receive Delete response
ReceivedIkePacket dummyDeleteIkeRespReceivedPacket =
makeDeleteIkeResponse(mSpyCurrentIkeSaRecord);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyDeleteIkeRespReceivedPacket);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
// 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);
}
@Ignore
public void disableTestIdleReceiveRequestWithFatalError() 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).onClosedWithException(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 ArrayList<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).onClosedWithException(any(InvalidSyntaxException.class));
}
@Test
public void testIdleHandlesDecryptPacketFailed() throws Exception {
setupIdleStateMachine();
ReceivedIkePacket packet =
makeDummyReceivedIkePacketWithUnprotectedError(
mSpyCurrentIkeSaRecord,
false /*isResp*/,
EXCHANGE_TYPE_INFORMATIONAL,
mock(IkeException.class));
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@Test
public void testHandlesUnencryptedPacket() throws Exception {
setupIdleStateMachine();
IkeMessage.setIkeMessageHelper(new IkeMessageHelper());
ReceivedIkePacket packet =
makeDummyUnencryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord.getLocalSpi(),
mSpyCurrentIkeSaRecord.getRemoteSpi(),
IkeHeader.EXCHANGE_TYPE_INFORMATIONAL,
false /*isResp*/,
false /*fromIkeInit*/,
new ArrayList<>());
mIkeSessionStateMachine.sendMessage(IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, packet);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
// Reset the IkeMessageHelper to be a mock. This is needed for #killSession (called in
// #tearDown), which attempts to notify the remote about the IKE session dying in the Idle
// state.
IkeMessage.setIkeMessageHelper(mMockIkeMessageHelper);
resetMockIkeMessageHelper();
}
@Test
public void testEapOnlyOption() throws Exception {
mIkeSessionStateMachine.quitNow();
IkeSessionParams ikeSessionParams =
buildIkeSessionParamsCommon()
.setAuthEap(mRootCertificate, mEapSessionConfig)
.addIkeOption(IKE_OPTION_EAP_ONLY_AUTH)
.build();
verifyIkeAuthWithNotifyPayload(ikeSessionParams, NOTIFY_TYPE_EAP_ONLY_AUTHENTICATION);
}
@Test
public void testInitialContact() throws Exception {
mIkeSessionStateMachine.quitNow();
IkeSessionParams ikeSessionParams =
buildIkeSessionParamsWithIkeOptions(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT);
verifyIkeAuthWithNotifyPayload(ikeSessionParams, NOTIFY_TYPE_INITIAL_CONTACT);
}
private void verifyIkeAuthWithNotifyPayload(
IkeSessionParams ikeSessionParams, int expectedNotifyType) throws Exception {
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(ikeSessionParams);
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
IkeMessage ikeAuthReqMessage = verifyAuthReqAndGetMsg();
List<IkeNotifyPayload> notifyPayloads =
ikeAuthReqMessage.getPayloadListForType(
IkePayload.PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
assertTrue(hasExpectedNotifyPayload(notifyPayloads, expectedNotifyType));
}
private IkeConfigPayload makeConfigPayload() throws Exception {
return (IkeConfigPayload)
IkeTestUtils.hexStringToIkePayload(
IkePayload.PAYLOAD_TYPE_CP, true /*isResp*/, CP_PAYLOAD_HEX_STRING);
}
private boolean hasExpectedNotifyPayload(
List<IkeNotifyPayload> notifyPayloads, int expectedNotifyType) {
for (IkeNotifyPayload payload : notifyPayloads) {
if (payload.notifyType == expectedNotifyType) {
return true;
}
}
return false;
}
@Test
public void testAcquireAndReleaseLocalReqWakeLock() throws Exception {
setupIdleStateMachine();
ChildSessionCallback cb = mock(ChildSessionCallback.class);
mIkeSessionStateMachine.openChildSession(mChildSessionParams, cb);
mLooper.dispatchAll();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
verify(mMockLocalRequestWakelock).acquire();
verify(mMockLocalRequestWakelock).release();
}
@Test
public void testQuitClearAllLocalReqWakeLocks() throws Exception {
final int localReqCnt = 3;
setupIdleStateMachine();
// Leave a Idle state so that the LocalRequest won't be executed
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mReceiving);
mLooper.dispatchAll();
// Only in test that all local requests will get the same WakeLock instance but in function
// code each local request will have a separate WakeLock.
for (int i = 0; i < localReqCnt; i++) {
mIkeSessionStateMachine.openChildSession(
mChildSessionParams, mock(ChildSessionCallback.class));
}
mLooper.dispatchAll();
verify(mMockLocalRequestWakelock, times(localReqCnt)).acquire();
mIkeSessionStateMachine.killSession();
mLooper.dispatchAll();
verify(mMockLocalRequestWakelock, times(localReqCnt)).release();
}
@Test
public void testIkeAuthWithN1Mode() throws Exception {
verifyIkeAuthWith3gppEnabled(
makeN1ModeInformationPayload(), 1 /* ike3gppDataListenerInvocations */);
verifyN1ModeReceived();
}
private void verifyIkeAuthWith3gppEnabled(
IkePayload ike3gppPayload, int ike3gppDataListenerInvocations) throws Exception {
// Quit and restart IKE Session with N1 Mode Capability params
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine =
makeAndStartIkeSession(buildIkeSessionParamsIke3gppExtension(PDU_SESSION_ID));
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
// Build IKE AUTH response with Auth-PSK, ID-Responder and config payloads.
List<IkePayload> authRelatedPayloads = new ArrayList<>();
authRelatedPayloads.add(ike3gppPayload);
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
authRelatedPayloads.add(makeConfigPayload());
verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
true /*hasChildPayloads*/,
true /*hasConfigPayloadInResp*/,
false /* isMobikeEnabled */,
true /* isIpv4 */,
ike3gppDataListenerInvocations);
verifyRetransmissionStopped();
}
private IkeNotifyPayload makeN1ModeInformationPayload() {
ByteBuffer n1ModeInformationBuffer = ByteBuffer.allocate(SNSSAI.length + 1);
n1ModeInformationBuffer.put((byte) SNSSAI.length);
n1ModeInformationBuffer.put(SNSSAI);
return new IkeNotifyPayload(
NOTIFY_TYPE_N1_MODE_INFORMATION, n1ModeInformationBuffer.array());
}
private void verifyN1ModeReceived() {
ArgumentCaptor<List<Ike3gppData>> ike3gppDataCaptor = ArgumentCaptor.forClass(List.class);
verify(mMockIke3gppDataListener).onIke3gppDataReceived(ike3gppDataCaptor.capture());
Ike3gppN1ModeInformation n1ModeInformation = null;
for (Ike3gppData payload : ike3gppDataCaptor.getValue()) {
if (payload.getDataType() == Ike3gppData.DATA_TYPE_NOTIFY_N1_MODE_INFORMATION) {
n1ModeInformation = (Ike3gppN1ModeInformation) payload;
}
}
assertNotNull(n1ModeInformation);
assertArrayEquals(SNSSAI, n1ModeInformation.getSnssai());
}
@Test
public void testIkeAuthWithBackoffTimerNetworkError() throws Exception {
verifyIkeAuthWithBackoffTimer(ERROR_TYPE_NETWORK_FAILURE);
}
@Test
public void testIkeAuthWithBackoffTimerNoApnSubscription() throws Exception {
verifyIkeAuthWithBackoffTimer(ERROR_TYPE_NO_APN_SUBSCRIPTION);
}
private void verifyIkeAuthWithBackoffTimer(int expectedNotifyErrorCause) throws Exception {
// Quit and restart IKE Session with N1 Mode Capability params
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine =
makeAndStartIkeSession(buildIkeSessionParamsIke3gppExtension(PDU_SESSION_ID));
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
// Build IKE AUTH response with BackoffTimer and Error-Notify
IkeNotifyPayload backoffTimerPayload =
new IkeNotifyPayload(NOTIFY_TYPE_BACKOFF_TIMER, BACKOFF_TIMER_DATA);
IkeNotifyPayload errorNotify = new IkeNotifyPayload(expectedNotifyErrorCause);
ReceivedIkePacket mockBackoffTimerResponsePacket =
makeIkeAuthRespWithoutChildPayloads(
Arrays.asList(backoffTimerPayload, errorNotify));
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, mockBackoffTimerResponsePacket);
mLooper.dispatchAll();
// Verify IKE Session is closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback)
.onClosedWithException(any(UnrecognizedIkeProtocolException.class));
verifyBackoffTimer(expectedNotifyErrorCause);
}
private void verifyBackoffTimer(int expectedNotifyErrorCause) {
ArgumentCaptor<List<Ike3gppData>> ike3gppDataCaptor = ArgumentCaptor.forClass(List.class);
verify(mMockIke3gppDataListener).onIke3gppDataReceived(ike3gppDataCaptor.capture());
Ike3gppBackoffTimer backoffTimer = null;
for (Ike3gppData payload : ike3gppDataCaptor.getValue()) {
if (payload.getDataType() == Ike3gppData.DATA_TYPE_NOTIFY_BACKOFF_TIMER) {
backoffTimer = (Ike3gppBackoffTimer) payload;
}
}
assertNotNull(backoffTimer);
assertEquals(BACKOFF_TIMER, backoffTimer.getBackoffTimer());
assertEquals(expectedNotifyErrorCause, backoffTimer.getBackoffCause());
}
@Test
public void testIkeAuthWithBackoffTimerWithoutError() throws Exception {
verifyIkeAuthWith3gppEnabled(
new IkeNotifyPayload(NOTIFY_TYPE_BACKOFF_TIMER, BACKOFF_TIMER_DATA),
0 /* ike3gppDataListenerInvocations */);
// BackoffTimer should be ignored
verify(mMockIke3gppDataListener, never()).onIke3gppDataReceived(any());
}
@Test(expected = IllegalArgumentException.class)
public void testIke3gppReuseCallback() throws Exception {
mIkeSessionStateMachine =
makeAndStartIkeSession(buildIkeSessionParamsIke3gppExtension(PDU_SESSION_ID));
makeAndStartIkeSession(buildIkeSessionParamsIke3gppExtension(PDU_SESSION_ID));
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabled() throws Exception {
verifyMobikeEnabled(true /* doesPeerSupportMobike */);
killSessionAndVerifyNetworkCallback(true /* expectCallbackUnregistered */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledPeerUnsupported() throws Exception {
verifyMobikeEnabled(false /* doesPeerSupportMobike */);
killSessionAndVerifyNetworkCallback(true /* expectCallbackUnregistered */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledWithEap() throws Exception {
List<IkePayload> authRelatedPayloads = new ArrayList<>();
authRelatedPayloads.add(new IkeNotifyPayload(NOTIFY_TYPE_MOBIKE_SUPPORTED));
verifyCreateIkeLocalIkeAuthPostEap(
buildIkeSessionParamsWithIkeOptions(IKE_OPTION_MOBIKE),
authRelatedPayloads,
true /* isMobikeEnabled */);
assertTrue(mIkeSessionStateMachine.mEnabledExtensions.contains(EXTENSION_TYPE_MOBIKE));
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledNattSupportedIpv4() throws Exception {
verifyMobikeEnabled(true /* doesPeerSupportNatt */, true /* isIpv4 */);
killSessionAndVerifyNetworkCallback(true /* expectCallbackUnregistered */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledNattUnsupportedIpv4() throws Exception {
verifyMobikeEnabled(false /* doesPeerSupportNatt */, true /* isIpv4 */);
killSessionAndVerifyNetworkCallback(true /* expectCallbackUnregistered */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledNattUnsupportedIpv6() throws Exception {
verifyMobikeEnabled(false /* doesPeerSupportNatt */, false /* isIpv4 */);
killSessionAndVerifyNetworkCallback(true /* expectCallbackUnregistered */);
}
/**
* Restarts the IkeSessionStateMachine with MOBIKE enabled. If doesPeerSupportMobike, MOBIKE
* will be active for the Session.
*
* @return the registered IkeDefaultNetworkCallack is returned if MOBIKE is active, else null
*/
@Nullable
private IkeDefaultNetworkCallback verifyMobikeEnabled(boolean doesPeerSupportMobike)
throws Exception {
// Can cast to IkeDefaultNetworkCallback because no Network is specified
return (IkeDefaultNetworkCallback)
verifyMobikeEnabled(doesPeerSupportMobike, null /* configuredNetwork */);
}
@Nullable
private IkeNetworkCallbackBase verifyMobikeEnabled(
boolean doesPeerSupportMobike, Network configuredNetwork) throws Exception {
return verifyMobikeEnabled(
doesPeerSupportMobike,
true /* doesPeerSupportNatt */,
true /* isIpv4 */,
false /* isEnforcePort4500*/,
configuredNetwork);
}
@Nullable
private IkeDefaultNetworkCallback verifyMobikeEnabled(
boolean doesPeerSupportNatt, boolean isIpv4) throws Exception {
// Can cast to IkeDefaultNetworkCallback because no Network is specified
return (IkeDefaultNetworkCallback)
verifyMobikeEnabled(
true /* doesPeerSupportMobike */,
doesPeerSupportNatt,
isIpv4,
false /* isEnforcePort4500*/,
null /* configuredNetwork */);
}
/** Returns the expected IkeSocket type when MOBIKE is supported by both sides */
private Class<? extends IkeSocket> getExpectedSocketType(
boolean doesPeerSupportNatt, boolean isEnforcePort4500, boolean isIpv4) {
if (doesPeerSupportNatt || isEnforcePort4500) {
if (isIpv4) {
return IkeUdpEncapSocket.class;
} else {
return IkeUdp6WithEncapPortSocket.class;
}
} else {
if (isIpv4) {
return IkeUdp4Socket.class;
} else {
return IkeUdp6Socket.class;
}
}
}
@Nullable
private IkeNetworkCallbackBase verifyMobikeEnabled(
boolean doesPeerSupportMobike,
boolean doesPeerSupportNatt,
boolean isIpv4,
boolean isEnforcePort4500,
Network configuredNetwork)
throws Exception {
mIkeSessionStateMachine =
restartStateMachineWithMobikeConfigured(
configuredNetwork, isEnforcePort4500, isIpv4);
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
// Enable force_udp_encap under IPv4 network because the kernel cannot process both
// UDP-encap and non-UDP-encap ESP packets for a single SA
boolean doesEnableForceUdpEncap = isIpv4;
if (isIpv4) {
if (doesPeerSupportNatt || doesEnableForceUdpEncap) {
// Either NAT detected or not detected won't affect the test since both cases
// indicate the server support NAT-T
mSpyIkeConnectionCtrl.handleNatDetectionResultInIkeInit(
true /* isNatDetected */, mSpyCurrentIkeSaRecord.getLocalSpi());
} else {
mSpyIkeConnectionCtrl.markSeverNattUnsupported();
}
} else {
// IKE client does not support IPv6 NAT-T and will not check if the server supports
// NAT-T when using IPv6 for IKE Session setup.
mSpyIkeConnectionCtrl.resetSeverNattSupport();
}
// Build IKE AUTH response. Include MOBIKE_SUPPORTED if doesPeerSupportMobike is true
List<IkePayload> authRelatedPayloads = new ArrayList<>();
IkeAuthPskPayload spyAuthPayload = makeSpyRespPskPayload();
authRelatedPayloads.add(spyAuthPayload);
IkeIdPayload respIdPayload = makeRespIdPayload();
authRelatedPayloads.add(respIdPayload);
authRelatedPayloads.add(makeConfigPayload());
if (doesPeerSupportMobike) {
authRelatedPayloads.add(new IkeNotifyPayload(NOTIFY_TYPE_MOBIKE_SUPPORTED));
}
IkeMessage ikeAuthReqMessage =
verifySharedKeyAuthentication(
spyAuthPayload,
respIdPayload,
authRelatedPayloads,
true /* hasChildPayloads */,
true /* hasConfigPayloadInResp */,
doesPeerSupportMobike,
isIpv4,
0 /* ike3gppDataListenerInvocations */);
verifyRetransmissionStopped();
boolean isMobikeSupportIndicated = false;
List<IkeNotifyPayload> reqNotifyPayloads =
ikeAuthReqMessage.getPayloadListForType(
PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
for (IkeNotifyPayload notifyPayload : reqNotifyPayloads) {
if (notifyPayload.notifyType == NOTIFY_TYPE_MOBIKE_SUPPORTED) {
isMobikeSupportIndicated = true;
break;
}
}
assertTrue(isMobikeSupportIndicated);
assertEquals(
doesPeerSupportMobike,
mIkeSessionStateMachine.mEnabledExtensions.contains(EXTENSION_TYPE_MOBIKE));
ArgumentCaptor<IkeNetworkCallbackBase> networkCallbackCaptor =
ArgumentCaptor.forClass(IkeNetworkCallbackBase.class);
// Expect different NetworkCallback registrations if there is a caller-configured Network
if (configuredNetwork == null) {
verify(mMockConnectManager)
.registerDefaultNetworkCallback(networkCallbackCaptor.capture(), any());
} else {
verify(mMockConnectManager)
.registerNetworkCallback(any(), networkCallbackCaptor.capture(), any());
}
IkeNetworkCallbackBase networkCallback = networkCallbackCaptor.getValue();
Class<? extends IkeNetworkCallbackBase> expectedCallbackType =
configuredNetwork == null
? IkeDefaultNetworkCallback.class
: IkeSpecificNetworkCallback.class;
assertTrue(expectedCallbackType.isInstance(networkCallback));
assertTrue(
getExpectedSocketType(
doesPeerSupportNatt || doesEnableForceUdpEncap,
isEnforcePort4500,
isIpv4)
.isInstance(mSpyIkeConnectionCtrl.getIkeSocket()));
return networkCallback;
}
private void killSessionAndVerifyNetworkCallback(boolean expectCallbackUnregistered) {
mIkeSessionStateMachine.killSession();
mLooper.dispatchAll();
verify(mMockConnectManager, expectCallbackUnregistered ? times(1) : never())
.unregisterNetworkCallback(any(IkeDefaultNetworkCallback.class));
}
/** Restarts the IkeSessionStateMachine with MOBIKE configured in the IkeSessionParams. */
private IkeSessionStateMachine restartStateMachineWithMobikeConfigured() throws Exception {
return restartStateMachineWithMobikeConfigured(
null /* network */, false /* isEnforcePort4500*/, true /* isIpv4 */);
}
private IkeSessionStateMachine restartStateMachineWithMobikeConfigured(
@Nullable Network configuredNetwork, boolean isEnforcePort4500, boolean isIpv4)
throws Exception {
mIkeSessionStateMachine.quitNow();
// makeAndStartIkeSession() expects no use of ConnectivityManager#getActiveNetwork when
// there is a configured Network. Use reset() to forget usage in setUp()
if (configuredNetwork != null) {
resetMockConnectManager();
}
// Set local and remote address
Network network = configuredNetwork == null ? mMockDefaultNetwork : configuredNetwork;
InetAddress localAddress = isIpv4 ? LOCAL_ADDRESS : LOCAL_ADDRESS_V6;
InetAddress remoteAddress = isIpv4 ? REMOTE_ADDRESS : REMOTE_ADDRESS_V6;
setupLocalAddressForNetwork(network, localAddress);
setupRemoteAddressForNetwork(network, remoteAddress);
IkeSessionParams.Builder ikeSessionParamsBuilder =
buildIkeSessionParamsCommon().setAuthPsk(mPsk).addIkeOption(IKE_OPTION_MOBIKE);
if (isEnforcePort4500) {
ikeSessionParamsBuilder.addIkeOption(IKE_OPTION_FORCE_PORT_4500);
}
if (configuredNetwork != null) {
ikeSessionParamsBuilder.setNetwork(configuredNetwork);
}
return makeAndStartIkeSession(ikeSessionParamsBuilder.build(), localAddress, remoteAddress);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeNetworkCallbackRegistrationFails() throws Exception {
doThrow(new RuntimeException("Failed to register IKE NetworkCallback"))
.when(mMockConnectManager)
.registerDefaultNetworkCallback(any(IkeDefaultNetworkCallback.class), any());
mIkeSessionStateMachine = restartStateMachineWithMobikeConfigured();
new IkeFirstAuthTestPretest().mockIkeInitAndTransitionToIkeAuth();
// Send IKE_AUTH resp and indicate MOBIKE support
List<IkePayload> authRelatedPayloads = new ArrayList<>();
authRelatedPayloads.add(makeSpyRespPskPayload());
authRelatedPayloads.add(makeRespIdPayload());
authRelatedPayloads.add(new IkeNotifyPayload(NOTIFY_TYPE_MOBIKE_SUPPORTED));
ReceivedIkePacket authResp = makeIkeAuthRespWithChildPayloads(authRelatedPayloads);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, authResp);
mLooper.dispatchAll();
verify(mMockConnectManager)
.registerDefaultNetworkCallback(any(IkeDefaultNetworkCallback.class), any());
verify(mMockIkeSessionCallback).onClosedWithException(any(IkeInternalException.class));
verify(mMockConnectManager).unregisterNetworkCallback(any(IkeDefaultNetworkCallback.class));
assertNull(mIkeSessionStateMachine.getCurrentState());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeEnabledNetworkDies() throws Exception {
IkeDefaultNetworkCallback callback = verifyMobikeEnabled(true /* doesPeerSupportMobike */);
callback.onLost(mMockDefaultNetwork);
ArgumentCaptor<IkeException> exceptionCaptor = ArgumentCaptor.forClass(IkeException.class);
verify(mMockIkeSessionCallback).onError(exceptionCaptor.capture());
IkeNetworkLostException cause = (IkeNetworkLostException) exceptionCaptor.getValue();
assertEquals(mMockDefaultNetwork, cause.getNetwork());
}
private void verifyMobikeActiveMobilityEvent(boolean isEnforcePort4500) throws Exception {
IkeDefaultNetworkCallback callback =
(IkeDefaultNetworkCallback)
verifyMobikeEnabled(
true /* doesPeerSupportMobike */,
false /* doesPeerSupportNatt */,
true /* isIpv4 */,
isEnforcePort4500,
null /* configuredNetwork */);
Network newNetwork = mockNewNetworkAndAddress(true /* isIpv4 */);
callback.onAvailable(newNetwork);
mLooper.dispatchAll();
verifyNetworkAndLocalAddressUpdated(
newNetwork, UPDATED_LOCAL_ADDRESS, REMOTE_ADDRESS, callback);
verify(mMockIkeLocalAddressGenerator)
.generateLocalAddress(
eq(newNetwork), eq(true /* isIpv4 */), eq(REMOTE_ADDRESS), anyInt());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeActiveMobilityEvent() throws Exception {
verifyMobikeActiveMobilityEvent(false /* isEnforcePort4500 */);
assertTrue(mSpyIkeConnectionCtrl.getIkeSocket() instanceof IkeUdpEncapSocket);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeActiveMobilityEventWithEnforcePort4500() throws Exception {
verifyMobikeActiveMobilityEvent(true /* isEnforcePort4500 */);
assertTrue(mSpyIkeConnectionCtrl.getIkeSocket() instanceof IkeUdpEncapSocket);
}
private Network mockNewNetworkAndAddress(boolean isIpv4) throws Exception {
InetAddress expectedRemoteAddress;
InetAddress injectedLocalAddress;
if (isIpv4) {
expectedRemoteAddress = REMOTE_ADDRESS;
injectedLocalAddress = UPDATED_LOCAL_ADDRESS;
} else {
expectedRemoteAddress = REMOTE_ADDRESS_V6;
injectedLocalAddress = UPDATED_LOCAL_ADDRESS_V6;
}
return mockNewNetworkAndAddress(isIpv4, injectedLocalAddress, expectedRemoteAddress);
}
private Network mockNewNetworkAndAddress(
boolean isIpv4, InetAddress localAddress, InetAddress remoteAddress) throws Exception {
return mockNewNetworkAndAddress(isIpv4, localAddress, remoteAddress, 1 /* dnsLookups */);
}
private Network mockNewNetworkAndAddress(
boolean isIpv4, InetAddress localAddress, InetAddress remoteAddress, int dnsLookups)
throws Exception {
Network newNetwork = mock(Network.class);
setupRemoteAddressForNetwork(newNetwork, remoteAddress);
setupDnsResolutionForNetwork(newNetwork, dnsLookups, remoteAddress);
setupLocalAddressForNetwork(newNetwork, localAddress);
return newNetwork;
}
private void verifyNetworkAndLocalAddressUpdated(
Network underlyingNetwork,
InetAddress localAddress,
InetAddress remoteAddress,
IkeNetworkCallbackBase networkCallback)
throws Exception {
assertEquals(underlyingNetwork, mSpyIkeConnectionCtrl.getNetwork());
assertEquals(underlyingNetwork, mSpyIkeConnectionCtrl.getNetwork());
assertEquals(localAddress, mSpyIkeConnectionCtrl.getLocalAddress());
assertEquals(remoteAddress, mSpyIkeConnectionCtrl.getRemoteAddress());
verifyIkeSaAddresses(
mIkeSessionStateMachine.mCurrentIkeSaRecord, localAddress, remoteAddress);
verify(underlyingNetwork, atLeastOnce()).getAllByName(REMOTE_HOSTNAME);
assertEquals(underlyingNetwork, networkCallback.getNetwork());
assertEquals(localAddress, networkCallback.getAddress());
}
private void verifyIkeSaAddresses(
IkeSaRecord saRecord, InetAddress localAddress, InetAddress remoteAddress) {
assertEquals(
localAddress, saRecord.getInitiatorIkeSecurityParameterIndex().getSourceAddress());
assertEquals(
remoteAddress, saRecord.getResponderIkeSecurityParameterIndex().getSourceAddress());
}
@Test(expected = IllegalArgumentException.class)
public void testSetNetworkNull() throws Exception {
mIkeSessionStateMachine.setNetwork(null);
}
@Test(expected = IllegalStateException.class)
public void testSetNetworkMobikeNotActive() throws Exception {
Network newNetwork = mock(Network.class);
mIkeSessionStateMachine.setNetwork(newNetwork);
}
@Test(expected = IllegalStateException.class)
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkMobikeActiveNetworkNotSpecified() throws Exception {
Network newNetwork = mock(Network.class);
verifyMobikeEnabled(true /* doesPeerSupportMobike */);
mIkeSessionStateMachine.setNetwork(newNetwork);
}
private void verifySetNetwork(
IkeNetworkCallbackBase callback, IkeSaRecord rekeySaRecord, State expectedState)
throws Exception {
verifySetNetwork(callback, rekeySaRecord, expectedState, true /* isIpv4 */);
}
private void verifySetNetwork(
IkeNetworkCallbackBase callback,
IkeSaRecord rekeySaRecord,
State expectedState,
boolean isIpv4)
throws Exception {
Network newNetwork = mockNewNetworkAndAddress(isIpv4);
mIkeSessionStateMachine.setNetwork(newNetwork);
mLooper.dispatchAll();
InetAddress expectedUpdatedLocalAddress =
isIpv4 ? UPDATED_LOCAL_ADDRESS : UPDATED_LOCAL_ADDRESS_V6;
InetAddress expectedRemoteAddress = isIpv4 ? REMOTE_ADDRESS : REMOTE_ADDRESS_V6;
verifyNetworkAndLocalAddressUpdated(
newNetwork, expectedUpdatedLocalAddress, expectedRemoteAddress, callback);
verify(mMockIkeLocalAddressGenerator)
.generateLocalAddress(
eq(newNetwork), eq(isIpv4), eq(expectedRemoteAddress), anyInt());
verify(mSpyIkeConnectionCtrl.getIkeSocket())
.registerIke(
mIkeSessionStateMachine.mCurrentIkeSaRecord.getLocalSpi(),
mSpyIkeConnectionCtrl);
if (rekeySaRecord != null) {
verifyIkeSaAddresses(rekeySaRecord, expectedUpdatedLocalAddress, expectedRemoteAddress);
verify(mSpyIkeConnectionCtrl.getIkeSocket())
.registerIke(rekeySaRecord.getLocalSpi(), mSpyIkeConnectionCtrl);
}
assertEquals(expectedState, mIkeSessionStateMachine.getCurrentState());
}
private IkeNetworkCallbackBase setupIdleStateMachineWithMobike() throws Exception {
return setupIdleStateMachineWithMobike(true /* doesPeerSupportNatt */, true /* isIpv4 */);
}
private IkeNetworkCallbackBase setupIdleStateMachineWithMobike(
boolean doesPeerSupportNatt, boolean isIpv4) throws Exception {
IkeNetworkCallbackBase callback =
verifyMobikeEnabled(
true /* doesPeerSupportMobike */,
doesPeerSupportNatt,
isIpv4,
false /* isEnforcePort4500*/,
mMockDefaultNetwork);
// reset IkeMessageHelper to make verifying outbound req easier
resetMockIkeMessageHelper();
mDummyChildSmCallback =
createChildAndGetChildSessionSmCallback(
mMockChildSessionStateMachine, CHILD_SPI_REMOTE, mMockChildSessionCallback);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
mLooper.dispatchAll();
return callback;
}
private void verifySetNetworkInIdleState(
boolean doesPeerSupportNatt,
boolean isIpv4BeforeNetworkChange,
boolean isIpv4AfterNetworkChange)
throws Exception {
IkeNetworkCallbackBase callback =
setupIdleStateMachineWithMobike(doesPeerSupportNatt, isIpv4BeforeNetworkChange);
verifySetNetwork(
callback,
null /* rekeySaRecord */,
mIkeSessionStateMachine.mMobikeLocalInfo,
isIpv4AfterNetworkChange);
assertTrue(
getExpectedSocketType(
doesPeerSupportNatt
|| isIpv4AfterNetworkChange /* force UDP-encap */,
false /* isEnforcePort4500*/,
isIpv4AfterNetworkChange)
.isInstance(mSpyIkeConnectionCtrl.getIkeSocket()));
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkInIdleStateNattSupportedIpv4ToIpv6() throws Exception {
verifySetNetworkInIdleState(
true /* doesPeerSupportNatt */,
true /* isIpv4BeforeNetworkChange */,
false /* isIpv4AfterNetworkChange */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkInIdleStateNattSupportedIpv6ToIpv4() throws Exception {
verifySetNetworkInIdleState(
true /* doesPeerSupportNatt */,
true /* isIpv4BeforeNetworkChange */,
false /* isIpv4AfterNetworkChange */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkInIdleStateNattSupportedIpv4ToIpv4() throws Exception {
verifySetNetworkInIdleState(
true /* doesPeerSupportNatt */,
true /* isIpv4BeforeNetworkChange */,
true /* isIpv4AfterNetworkChange */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkInIdleStateNattUnsupportedIpv4ToIpv4() throws Exception {
verifySetNetworkInIdleState(
false /* doesPeerSupportNatt */,
true /* isIpv4BeforeNetworkChange */,
true /* isIpv4AfterNetworkChange */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkInIdleStateNattUnsupportedIpv6ToIpv6() throws Exception {
verifySetNetworkInIdleState(
false /* doesPeerSupportNatt */,
false /* isIpv4BeforeNetworkChange */,
false /* isIpv4AfterNetworkChange */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkLocalRekeyState() throws Exception {
// Start IKE Session + transition to Idle
IkeNetworkCallbackBase callback =
verifyMobikeEnabled(true /* doesPeerSupportMobike */, mMockDefaultNetwork);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mIdle);
verifyRekeyIkeLocalCreateHandlesResponse();
verifySetNetwork(
callback,
mIkeSessionStateMachine.mLocalInitNewIkeSaRecord,
mIkeSessionStateMachine.mRekeyIkeLocalDelete);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetNetworkRemoteRekeyState() throws Exception {
// Start IKE Session + transition to remote rekey
IkeNetworkCallbackBase callback =
verifyMobikeEnabled(true /* doesPeerSupportMobike */, mMockDefaultNetwork);
mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord = mSpyRemoteInitIkeSaRecord;
mIkeSessionStateMachine.addIkeSaRecord(mSpyRemoteInitIkeSaRecord);
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_FORCE_TRANSITION,
mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
mLooper.dispatchAll();
verifySetNetwork(
callback,
mIkeSessionStateMachine.mRemoteInitNewIkeSaRecord,
mIkeSessionStateMachine.mRekeyIkeRemoteDelete);
}
private void verifyRcvDeviceIdentityCheckReqAndReply() throws Exception {
// Receive a Information req with DEVICE_IDENTITY payload
ReceivedIkePacket dummyRequest = makeDeviceIdentityIkeRequest();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRequest);
mLooper.dispatchAll();
IkeMessage resp = verifyAndGetOutboundInformationalResp();
List<IkeNotifyPayload> notifyPayloads =
resp.getPayloadListForType(PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
assertEquals(1, notifyPayloads.size());
IkeNotifyPayload deviceIdentity = notifyPayloads.get(0);
assertEquals(NOTIFY_TYPE_DEVICE_IDENTITY, deviceIdentity.notifyType);
assertArrayEquals(DEVICE_IDENTITY_PAYLOAD_IMEI, deviceIdentity.notifyData);
}
private void verifyRcvRoutabilityCheckReqAndReply() throws Exception {
// Receive a routability check request
ReceivedIkePacket dummyRequest = makeRoutabilityCheckIkeRequest();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_RECEIVE_IKE_PACKET, dummyRequest);
mLooper.dispatchAll();
IkeMessage resp = verifyAndGetOutboundInformationalResp();
List<IkeNotifyPayload> notifyPayloads =
resp.getPayloadListForType(PAYLOAD_TYPE_NOTIFY, IkeNotifyPayload.class);
assertEquals(1, notifyPayloads.size());
IkeNotifyPayload cookie2 = notifyPayloads.get(0);
assertEquals(NOTIFY_TYPE_COOKIE2, cookie2.notifyType);
assertArrayEquals(COOKIE2_DATA, cookie2.notifyData);
}
@Test
public void testRcvRoutabilityCheckReqInIdle() throws Exception {
setupIdleStateMachine();
verifyRcvRoutabilityCheckReqAndReply();
assertTrue(
mIkeSessionStateMachine.getCurrentState() instanceof IkeSessionStateMachine.Idle);
}
@Test
public void testRcvRoutabilityCheckReqInDpd() throws Exception {
executeAndVerifySendLocalDPD();
verifyRcvRoutabilityCheckReqAndReply();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.DpdIkeLocalInfo);
}
@Test
public void testRcvRoutabilityCheckReqInChildProcedureOngoing() throws Exception {
setupIdleStateMachine();
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getChildLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_DELETE_CHILD,
mMockChildSessionCallback,
null /*childParams*/));
mLooper.dispatchAll();
// For conveniency to verify outbound routability check response, reset IkeMessageHelper to
// forget sending Delete Child request
resetMockIkeMessageHelper();
verifyRcvRoutabilityCheckReqAndReply();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
}
@Test
public void testRcvRoutabilityCheckReqInLocalRekeyIkeCreate() throws Exception {
setupIdleStateMachine();
// Send Rekey-Create request
mIkeSessionStateMachine.sendMessage(
IkeSessionStateMachine.CMD_EXECUTE_LOCAL_REQ,
mLocalRequestFactory.getIkeLocalRequest(
IkeSessionStateMachine.CMD_LOCAL_REQUEST_REKEY_IKE));
mLooper.dispatchAll();
// For conveniency to verify outbound routability check response, reset IkeMessageHelper to
// forget sending Rekey Create request
resetMockIkeMessageHelper();
verifyRcvRoutabilityCheckReqAndReply();
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.RekeyIkeLocalCreate);
}
private void verifyMobikeLocalInfoSendsRequest(
boolean migrateToIpv4,
boolean natSupported,
boolean natDetected,
boolean expectNatDetection)
throws Exception {
// IKE Session is set up with IPv4 address and with NAT detected
setupIdleStateMachineWithMobike();
if (natSupported) {
mSpyIkeConnectionCtrl.setNatDetected(natDetected);
} else {
mSpyIkeConnectionCtrl.markSeverNattUnsupported();
}
if (!migrateToIpv4) {
mSpyIkeConnectionCtrl.setLocalAddress(LOCAL_ADDRESS_V6);
mSpyIkeConnectionCtrl.setRemoteAddress(REMOTE_ADDRESS_V6);
}
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
verifyUpdateSaAddressesReq(expectNatDetection);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNatDetectedIpv4() throws Exception {
verifyMobikeLocalInfoSendsRequest(
true /* migrateToIpv4 */,
true /* natSupported */,
true /* natDetected */,
true /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNatNotDetectedIpv4() throws Exception {
verifyMobikeLocalInfoSendsRequest(
true /* migrateToIpv4 */,
true /* natSupported */,
false /* natDetected */,
true /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNattUnsupportedIpv4() throws Exception {
verifyMobikeLocalInfoSendsRequest(
true /* migrateToIpv4 */,
false /* natSupported */,
false /* natDetected */,
false /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNatDetectedIpv6() throws Exception {
verifyMobikeLocalInfoSendsRequest(
false /* migrateToIpv4 */,
true /* natSupported */,
true /* natDetected */,
true /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNatNotDetectedIpv6() throws Exception {
verifyMobikeLocalInfoSendsRequest(
false /* migrateToIpv4 */,
true /* natSupported */,
false /* natDetected */,
false /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoSendsRequestNattUnsupportedIpv6() throws Exception {
verifyMobikeLocalInfoSendsRequest(
false /* migrateToIpv4 */,
false /* natSupported */,
false /* natDetected */,
false /* expectNatDetection */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoHandlesResponseWithNatDetection() throws Exception {
setupIdleStateMachineWithMobike();
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
verifyUpdateSaAddressesResp(
true /* expectNatDetected */,
mSpyIkeConnectionCtrl.getNetwork(),
mSpyIkeConnectionCtrl.getLocalAddress(),
mSpyIkeConnectionCtrl.getRemoteAddress());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoHandlesResponseWithoutNatDetection() throws Exception {
setupIdleStateMachineWithMobike();
mSpyIkeConnectionCtrl.markSeverNattUnsupported();
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
verifyUpdateSaAddressesResp(
false /* expectNatDetected */,
mSpyIkeConnectionCtrl.getNetwork(),
mSpyIkeConnectionCtrl.getLocalAddress(),
mSpyIkeConnectionCtrl.getRemoteAddress());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoHandlesResponseWithNatDetectedIpv6() throws Exception {
setupIdleStateMachineWithMobike();
// Migrate to Ipv6
mSpyIkeConnectionCtrl.setLocalAddress(LOCAL_ADDRESS_V6);
mSpyIkeConnectionCtrl.setRemoteAddress(REMOTE_ADDRESS_V6);
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
// Send response with NAT_DETECTION payloads
List<Integer> respPayloadTypeList = new ArrayList<>();
List<String> respPayloadHexStringList = new ArrayList<>();
respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
respPayloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
respPayloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
ReceivedIkePacket respIkePacket =
makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
EXCHANGE_TYPE_INFORMATIONAL,
true /* isResp */,
respPayloadTypeList,
respPayloadHexStringList);
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, respIkePacket);
mLooper.dispatchAll();
// Verify IKE Session was closed properly
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback).onClosedWithException(any(IkeInternalException.class));
}
private void verifyUpdateSaAddressesReq(boolean expectNatDetection) throws Exception {
List<IkePayload> reqPayloadList = verifyOutInfoMsgHeaderAndGetPayloads(false /* isResp */);
int expectedPayloads = expectNatDetection ? 3 : 1;
assertEquals(expectedPayloads, reqPayloadList.size());
assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_UPDATE_SA_ADDRESSES));
if (expectNatDetection) {
assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_NAT_DETECTION_SOURCE_IP));
assertTrue(isNotifyExist(reqPayloadList, NOTIFY_TYPE_NAT_DETECTION_DESTINATION_IP));
}
}
private void verifyUpdateSaAddressesResp(
boolean expectNatDetected,
Network expectedNetwork,
InetAddress expectedLocalAddr,
InetAddress expectedRemoteAddr)
throws Exception {
List<Integer> respPayloadTypeList = new ArrayList<>();
List<String> respPayloadHexStringList = new ArrayList<>();
// For simplicity, if the test tries to verify a case when there is not NAT detected, just
// do not include NAT Detection payload in the response
if (!expectNatDetected) {
respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
respPayloadHexStringList.add(NAT_DETECTION_SOURCE_PAYLOAD_HEX_STRING);
respPayloadTypeList.add(PAYLOAD_TYPE_NOTIFY);
respPayloadHexStringList.add(NAT_DETECTION_DESTINATION_PAYLOAD_HEX_STRING);
}
ReceivedIkePacket respIkePacket =
makeDummyEncryptedReceivedIkePacket(
mSpyCurrentIkeSaRecord,
EXCHANGE_TYPE_INFORMATIONAL,
true /* isResp */,
respPayloadTypeList,
respPayloadHexStringList);
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, respIkePacket);
mLooper.dispatchAll();
int expectedNatStatus = expectNatDetected ? NAT_DETECTED : NAT_TRAVERSAL_UNSUPPORTED;
assertEquals(expectedNatStatus, mSpyIkeConnectionCtrl.getNatStatus());
ArgumentCaptor<IkeSessionConnectionInfo> connectionInfoCaptor =
ArgumentCaptor.forClass(IkeSessionConnectionInfo.class);
verify(mMockIkeSessionCallback)
.onIkeSessionConnectionInfoChanged(connectionInfoCaptor.capture());
IkeSessionConnectionInfo newConnectionInfo = connectionInfoCaptor.getValue();
assertEquals(expectedNetwork, newConnectionInfo.getNetwork());
assertEquals(expectedLocalAddr, newConnectionInfo.getLocalAddress());
assertEquals(expectedRemoteAddr, newConnectionInfo.getRemoteAddress());
// TODO(b/172015298): verify IPsec SAs migrated instead of rekey when kernel supports it
// Verify that Child Rekey (MOBIKE) initiated after successful UPDATE_SA_ADDRESSES resp
assertTrue(
mIkeSessionStateMachine.getCurrentState()
instanceof IkeSessionStateMachine.ChildProcedureOngoing);
verify(mMockChildSessionStateMachine)
.rekeyChildSessionForMobike(eq(expectedLocalAddr), eq(expectedRemoteAddr), any());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testNattKeepaliveStoppedDuringMobilityEvent() throws Exception {
// IKE Session established with IPv4 address and a NAT detected
IkeNetworkCallbackBase callback = setupIdleStateMachineWithMobike();
verifySetNetwork(
callback,
null /* rekeySaRecord */,
mIkeSessionStateMachine.mMobikeLocalInfo,
false /* isIpv4 */);
// Keepalive for the old UDP encap socket stopped
verify(mMockIkeNattKeepalive).stop();
// Keepalive is no longer needed since IKE Session switches to IPv6
assertNull(mSpyIkeConnectionCtrl.getIkeNattKeepalive());
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoHandlesDeleteRequest() throws Exception {
setupIdleStateMachineWithMobike();
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
// Reset to ignore UPDATE_SA_ADDRESSES req sent when entering MobikeLocalInfo
resetMockIkeMessageHelper();
// Reset to ignore IkeSessionCallback#onOpened from setting up the state machine
resetSpyUserCbExecutor();
verifyIkeDeleteRequestHandled();
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testMobikeLocalInfoHandlesNonDeleteRequest() throws Exception {
setupIdleStateMachineWithMobike();
mIkeSessionStateMachine.sendMessage(
CMD_FORCE_TRANSITION, mIkeSessionStateMachine.mMobikeLocalInfo);
mLooper.dispatchAll();
// Reset to ignore UPDATE_SA_ADDRESSES req sent when entering MobikeLocalInfo
resetMockIkeMessageHelper();
mIkeSessionStateMachine.sendMessage(
CMD_RECEIVE_IKE_PACKET, makeRoutabilityCheckIkeRequest());
mLooper.dispatchAll();
verifyIncrementRemoteReqMsgId();
List<IkePayload> respPayloads = verifyOutInfoMsgHeaderAndGetPayloads(true /* isResp */);
assertEquals(1, respPayloads.size());
IkeNotifyPayload notifyPayload = (IkeNotifyPayload) respPayloads.get(0);
assertEquals(ERROR_TYPE_TEMPORARY_FAILURE, notifyPayload.notifyType);
assertEquals(
mIkeSessionStateMachine.mMobikeLocalInfo,
mIkeSessionStateMachine.getCurrentState());
}
private void setupAndVerifyDnsLookupsOnSetNetwork(
int dnsLookupsForSuccess, int expectedDnsLookups, boolean expectSessionClosed)
throws Exception {
final IkeNetworkCallbackBase callback = setupIdleStateMachineWithMobike();
final Network newNetwork =
mockNewNetworkAndAddress(
false /* isIpv4 */,
UPDATED_LOCAL_ADDRESS_V6,
REMOTE_ADDRESS_V6,
dnsLookupsForSuccess);
mIkeSessionStateMachine.setNetwork(newNetwork);
mLooper.dispatchAll();
verify(newNetwork, times(expectedDnsLookups)).getAllByName(REMOTE_HOSTNAME);
if (expectSessionClosed) {
assertNull(mIkeSessionStateMachine.getCurrentState());
verify(mMockIkeSessionCallback)
.onClosedWithException(
argThat(
e ->
e instanceof IkeInternalException
&& e.getCause() instanceof IOException));
} else {
assertTrue(mSpyIkeConnectionCtrl.getAllRemoteIpv4Addresses().isEmpty());
assertEquals(
Arrays.asList(REMOTE_ADDRESS_V6),
mSpyIkeConnectionCtrl.getAllRemoteIpv6Addresses());
verifyNetworkAndLocalAddressUpdated(
newNetwork, UPDATED_LOCAL_ADDRESS_V6, REMOTE_ADDRESS_V6, callback);
}
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testDnsLookupOnSetNetwork() throws Exception {
setupAndVerifyDnsLookupsOnSetNetwork(
1 /* dnsLookupsForSuccess */,
1 /* expectedDnsLookups */,
false /* expectSessionClosed */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testDnsLookupOnSetNetworkWithDnsRetries() throws Exception {
setupAndVerifyDnsLookupsOnSetNetwork(
2 /* dnsLookupsForSuccess */,
2 /* expectedDnsLookups */,
false /* expectSessionClosed */);
}
@Test
@SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testDnsLookupOnSetNetworkWithDnsFailure() throws Exception {
// Require more lookups for successful DNS than IKE allows to force failure
setupAndVerifyDnsLookupsOnSetNetwork(
4 /* dnsLookupsForSuccess */,
3 /* expectedDnsLookups */,
true /* expectSessionClosed */);
}
@Test
public void testNattKeepaliveStarts() throws Exception {
IkeSessionParams sessionParams =
buildIkeSessionParamsCommon()
.setAuthPsk(mPsk)
.setNattKeepAliveDelaySeconds(NATT_KEEPALIVE_DELAY)
.build();
// Restart IkeSessionStateMachine with NATT Keepalive delay configured
setupFirstIkeSa();
mIkeSessionStateMachine.quitNow();
mIkeSessionStateMachine = makeAndStartIkeSession(sessionParams);
mIkeSessionStateMachine.openSession();
mLooper.dispatchAll();
mIkeSessionStateMachine.sendMessage(CMD_RECEIVE_IKE_PACKET, makeIkeInitResponse());
mLooper.dispatchAll();
verify(mMockIkeNattKeepalive).start();
}
}