Initial CL for testing IkeSession creation

This commit:
-Extend TunUtils for processing IKE packets
-Add IkeSessionBaseTest containing common functionality for all IkeSession tests
-Add end-to-end test for IKEv2 PSK verifying creating IKE SA, creating child SAs
 and closing sessions
-Add basic tests for error scenarios

Bug: 148689509
Test: atest CtsIkeTestCases
Change-Id: Ie6c18591ffcc883abbf0484d9a59dfda61b33257
This commit is contained in:
evitayan
2020-04-07 15:16:08 -07:00
parent 6c63b1eaac
commit 653d7ebb44
8 changed files with 904 additions and 102 deletions

View File

@@ -33,6 +33,7 @@ android_test {
"androidx.test.ext.junit",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"net-tests-utils",
],
platform_apis: true,

View File

@@ -46,6 +46,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.net.ipsec.ike.testutils.CertUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,7 +64,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
public final class IkeSessionParamsTest extends IkeSessionTestBase {
private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(20L);
private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(10L);
private static final int DPD_DELAY_SECONDS = (int) TimeUnit.MINUTES.toSeconds(10L);
@@ -105,6 +106,9 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
@Before
public void setUp() throws Exception {
// This address is never used except for setting up the test network
setUpTestNetwork(IPV4_ADDRESS_LOCAL);
mServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem");
mClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem");
mClientIntermediateCaCertOne =
@@ -114,6 +118,11 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
mClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key");
}
@After
public void tearDown() throws Exception {
tearDownTestNetwork();
}
private static EapSessionConfig.Builder createEapOnlySafeMethodsBuilder() {
return new EapSessionConfig.Builder()
.setEapIdentity(EAP_IDENTITY)
@@ -131,7 +140,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
*/
private IkeSessionParams.Builder createIkeParamsBuilderMinimum() {
return new IkeSessionParams.Builder(sContext)
.setNetwork(sTunNetwork)
.setNetwork(mTunNetwork)
.setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
.addSaProposal(SA_PROPOSAL)
.setLocalIdentification(LOCAL_ID)
@@ -145,7 +154,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
* @see #createIkeParamsBuilderMinimum
*/
private void verifyIkeParamsMinimum(IkeSessionParams sessionParams) {
assertEquals(sTunNetwork, sessionParams.getNetwork());
assertEquals(mTunNetwork, sessionParams.getNetwork());
assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());
@@ -268,7 +277,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
*/
private IkeSessionParams.Builder createIkeParamsBuilderMinimumWithoutAuth() {
return new IkeSessionParams.Builder(sContext)
.setNetwork(sTunNetwork)
.setNetwork(mTunNetwork)
.setServerHostname(IPV4_ADDRESS_REMOTE.getHostAddress())
.addSaProposal(SA_PROPOSAL)
.setLocalIdentification(LOCAL_ID)
@@ -282,7 +291,7 @@ public final class IkeSessionParamsTest extends IkeSessionParamsTestBase {
* @see #createIkeParamsBuilderMinimumWithoutAuth
*/
private void verifyIkeParamsMinimumWithoutAuth(IkeSessionParams sessionParams) {
assertEquals(sTunNetwork, sessionParams.getNetwork());
assertEquals(mTunNetwork, sessionParams.getNetwork());
assertEquals(IPV4_ADDRESS_REMOTE.getHostAddress(), sessionParams.getServerHostname());
assertEquals(Arrays.asList(SA_PROPOSAL), sessionParams.getSaProposals());
assertEquals(LOCAL_ID, sessionParams.getLocalIdentification());

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2020 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 android.net.ipsec.ike.cts;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.Network;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
abstract class IkeSessionParamsTestBase extends IkeTestBase {
// Static state to reduce setup/teardown
static ConnectivityManager sCM;
static TestNetworkManager sTNM;
static ParcelFileDescriptor sTunFd;
static TestNetworkCallback sTunNetworkCallback;
static Network sTunNetwork;
static Context sContext = InstrumentationRegistry.getContext();
static IBinder sBinder = new Binder();
// This method is guaranteed to run in subclasses and will run before subclasses' @BeforeClass
// methods.
@BeforeClass
public static void setUpTestNetworkBeforeClass() throws Exception {
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity();
sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
TestNetworkInterface testIface =
sTNM.createTunInterface(
new LinkAddress[] {new LinkAddress(IPV4_ADDRESS_LOCAL, IP4_PREFIX_LEN)});
sTunFd = testIface.getFileDescriptor();
sTunNetworkCallback =
TestNetworkUtils.setupAndGetTestNetwork(
sCM, sTNM, testIface.getInterfaceName(), sBinder);
sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
}
// This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
// methods.
@AfterClass
public static void tearDownTestNetworkAfterClass() throws Exception {
sCM.unregisterNetworkCallback(sTunNetworkCallback);
sTNM.teardownTestNetwork(sTunNetwork);
sTunFd.close();
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
}
}

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2020 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 android.net.ipsec.ike.cts;
import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION;
import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_INET6;
import static com.android.internal.util.HexDump.hexStringToByteArray;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.IkeFqdnIdentification;
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.platform.test.annotations.AppModeFull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
public class IkeSessionPskTest extends IkeSessionTestBase {
// Test vectors for success workflow
private static final String SUCCESS_IKE_INIT_RESP =
"46B8ECA1E0D72A18B45427679F9245D421202220000000000000015022000030"
+ "0000002C010100040300000C0100000C800E0080030000080300000203000008"
+ "0200000200000008040000022800008800020000A7AA3435D088EC1A2B7C2A47"
+ "1FA1B85F1066C9B2006E7C353FB5B5FDBC2A88347ED2C6F5B7A265D03AE34039"
+ "6AAC0145CFCC93F8BDB219DDFF22A603B8856A5DC59B6FAB7F17C5660CF38670"
+ "8794FC72F273ADEB7A4F316519794AED6F8AB61F95DFB360FAF18C6C8CABE471"
+ "6E18FE215348C2E582171A57FC41146B16C4AFE429000024A634B61C0E5C90C6"
+ "8D8818B0955B125A9B1DF47BBD18775710792E651083105C2900001C00004004"
+ "406FA3C5685A16B9B72C7F2EEE9993462C619ABE2900001C00004005AF905A87"
+ "0A32222AA284A7070585601208A282F0290000080000402E290000100000402F"
+ "00020003000400050000000800004014";
private static final String SUCCESS_IKE_AUTH_RESP =
"46B8ECA1E0D72A18B45427679F9245D42E20232000000001000000EC240000D0"
+ "0D06D37198F3F0962DE8170D66F1A9008267F98CDD956D984BDCED2FC7FAF84A"
+ "A6664EF25049B46B93C9ED420488E0C172AA6635BF4011C49792EF2B88FE7190"
+ "E8859FEEF51724FD20C46E7B9A9C3DC4708EF7005707A18AB747C903ABCEAC5C"
+ "6ECF5A5FC13633DCE3844A920ED10EF202F115DBFBB5D6D2D7AB1F34EB08DE7C"
+ "A54DCE0A3A582753345CA2D05A0EFDB9DC61E81B2483B7D13EEE0A815D37252C"
+ "23D2F29E9C30658227D2BB0C9E1A481EAA80BC6BE9006BEDC13E925A755A0290"
+ "AEC4164D29997F52ED7DCC2E";
private static final String SUCCESS_CREATE_CHILD_RESP =
"46B8ECA1E0D72A18B45427679F9245D42E20242000000002000000CC210000B0"
+ "484565D4AF6546274674A8DE339E9C9584EE2326AB9260F41C4D0B6C5B02D1D"
+ "2E8394E3CDE3094895F2ACCABCDCA8E82960E5196E9622BD13745FC8D6A2BED"
+ "E561FF5D9975421BC463C959A3CBA3478256B6D278159D99B512DDF56AC1658"
+ "63C65A986F395FE8B1476124B91F83FD7865304EB95B22CA4DD9601DA7A2533"
+ "ABF4B36EB1B8CD09522F6A600032316C74E562E6756D9D49D945854E2ABDC4C"
+ "3AF36305353D60D40B58BE44ABF82";
private static final String SUCCESS_DELETE_CHILD_RESP =
"46B8ECA1E0D72A18B45427679F9245D42E202520000000030000004C2A000030"
+ "0C5CEB882DBCA65CE32F4C53909335F1365C91C555316C5E9D9FB553F7AA916"
+ "EF3A1D93460B7FABAF0B4B854";
private static final String SUCCESS_DELETE_IKE_RESP =
"46B8ECA1E0D72A18B45427679F9245D42E202520000000040000004C00000030"
+ "9352D71100777B00ABCC6BD7DBEA697827FFAAA48DF9A54D1D68161939F5DC8"
+ "6743A7CEB2BE34AC00095A5B8";
private static final long IKE_INIT_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
private static final TunnelModeChildSessionParams CHILD_PARAMS =
new TunnelModeChildSessionParams.Builder()
.addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher())
.addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher())
.addInternalAddressRequest(AF_INET)
.addInternalAddressRequest(AF_INET6)
.build();
private IkeSessionParams createIkeSessionParams(InetAddress mRemoteAddress) {
return new IkeSessionParams.Builder(sContext)
.setNetwork(mTunNetwork)
.setServerHostname(mRemoteAddress.getHostAddress())
.addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher())
.addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher())
.setLocalIdentification(new IkeFqdnIdentification(LOCAL_HOSTNAME))
.setRemoteIdentification(new IkeFqdnIdentification(REMOTE_HOSTNAME))
.setAuthPsk(IKE_PSK)
.build();
}
private IkeSession openIkeSession(IkeSessionParams ikeParams) {
return new IkeSession(
sContext,
ikeParams,
CHILD_PARAMS,
mUserCbExecutor,
mIkeSessionCallback,
mFirstChildSessionCallback);
}
@Test
public void testIkeSessionSetupAndManageChildSas() throws Exception {
// Open IKE Session
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
int expectedMsgId = 0;
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
false /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
// Verify opening IKE Session
IkeSessionConfiguration ikeConfig = mIkeSessionCallback.awaitIkeConfig();
assertNotNull(ikeConfig);
assertEquals(EXPECTED_REMOTE_APP_VERSION_EMPTY, ikeConfig.getRemoteApplicationVersion());
assertTrue(ikeConfig.getRemoteVendorIds().isEmpty());
assertTrue(ikeConfig.getPcscfServers().isEmpty());
assertTrue(ikeConfig.isIkeExtensionEnabled(EXTENSION_TYPE_FRAGMENTATION));
IkeSessionConnectionInfo ikeConnectInfo = ikeConfig.getIkeSessionConnectionInfo();
assertNotNull(ikeConnectInfo);
assertEquals(mLocalAddress, ikeConnectInfo.getLocalAddress());
assertEquals(mRemoteAddress, ikeConnectInfo.getRemoteAddress());
assertEquals(mTunNetwork, ikeConnectInfo.getNetwork());
// Verify opening first Child Session
ChildSessionConfiguration firstChildConfig = mFirstChildSessionCallback.awaitChildConfig();
assertNotNull(firstChildConfig);
assertEquals(
Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
assertEquals(
Arrays.asList(EXPECTED_INTERNAL_LINK_ADDR),
firstChildConfig.getInternalAddresses());
assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
// Open additional Child Session
TestChildSessionCallback additionalChildCb = new TestChildSessionCallback();
ikeSession.openChildSession(CHILD_PARAMS, additionalChildCb);
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_CREATE_CHILD_RESP));
// Verify opening additional Child Session
ChildSessionConfiguration additionalChildConfig = additionalChildCb.awaitChildConfig();
assertNotNull(additionalChildConfig);
assertEquals(
Arrays.asList(EXPECTED_INBOUND_TS), firstChildConfig.getInboundTrafficSelectors());
assertEquals(Arrays.asList(DEFAULT_V4_TS), firstChildConfig.getOutboundTrafficSelectors());
assertTrue(additionalChildConfig.getInternalAddresses().isEmpty());
assertTrue(firstChildConfig.getInternalSubnets().isEmpty());
assertTrue(firstChildConfig.getInternalDnsServers().isEmpty());
assertTrue(firstChildConfig.getInternalDhcpServers().isEmpty());
// Close additional Child Session
ikeSession.closeChildSession(additionalChildCb);
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_DELETE_CHILD_RESP));
additionalChildCb.awaitOnClosed();
// Close IKE Session
ikeSession.close();
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_DELETE_IKE_RESP));
mFirstChildSessionCallback.awaitOnClosed();
mIkeSessionCallback.awaitOnClosed();
// TODO: verify IpSecTransform pair is created and deleted
}
@Test
public void testIkeSessionKill() throws Exception {
// Open IKE Session
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
int expectedMsgId = 0;
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
false /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_IKE_INIT_RESP));
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
true /* expectedUseEncap */,
hexStringToByteArray(SUCCESS_IKE_AUTH_RESP));
ikeSession.kill();
mFirstChildSessionCallback.awaitOnClosed();
mIkeSessionCallback.awaitOnClosed();
}
@Test
public void testIkeInitFail() throws Exception {
String ikeInitFailRespHex =
"46B8ECA1E0D72A180000000000000000292022200000000000000024000000080000000E";
// Open IKE Session
IkeSession ikeSession = openIkeSession(createIkeSessionParams(mRemoteAddress));
int expectedMsgId = 0;
mTunUtils.awaitReqAndInjectResp(
IKE_INIT_SPI,
expectedMsgId++,
false /* expectedUseEncap */,
hexStringToByteArray(ikeInitFailRespHex));
IkeException exception = mIkeSessionCallback.awaitOnClosedException();
assertNotNull(exception);
assertTrue(exception instanceof IkeProtocolException);
IkeProtocolException protocolException = (IkeProtocolException) exception;
assertEquals(ERROR_TYPE_NO_PROPOSAL_CHOSEN, protocolException.getErrorType());
assertArrayEquals(EXPECTED_PROTOCOL_ERROR_DATA_NONE, protocolException.getErrorData());
}
// TODO(b/148689509): Verify rekey process and handling IKE_AUTH failure
}

View File

@@ -0,0 +1,374 @@
/*
* Copyright (C) 2020 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
*
* 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 android.net.ipsec.ike.cts;
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.annotations.PolicyDirection;
import android.net.ipsec.ike.ChildSessionCallback;
import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.AppModeFull;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
import com.android.testutils.ArrayTrackRecord;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Package private base class for testing IkeSessionParams and IKE exchanges.
*
* <p>Subclasses MUST explicitly call #setUpTestNetwork and #tearDownTestNetwork to be able to use
* the test network
*/
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
abstract class IkeSessionTestBase extends IkeTestBase {
// Package-wide common expected results that will be shared by all IKE/Child SA creation tests
static final String EXPECTED_REMOTE_APP_VERSION_EMPTY = "";
static final byte[] EXPECTED_PROTOCOL_ERROR_DATA_NONE = new byte[0];
static final InetAddress EXPECTED_INTERNAL_ADDR =
InetAddresses.parseNumericAddress("198.51.100.10");
static final LinkAddress EXPECTED_INTERNAL_LINK_ADDR =
new LinkAddress(EXPECTED_INTERNAL_ADDR, IP4_PREFIX_LEN);
static final IkeTrafficSelector EXPECTED_INBOUND_TS =
new IkeTrafficSelector(
MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR);
// Static state to reduce setup/teardown
static Context sContext = InstrumentationRegistry.getContext();
static ConnectivityManager sCM =
(ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
static TestNetworkManager sTNM;
private static final int TIMEOUT_MS = 500;
// Constants to be used for providing different IP addresses for each tests
private static final byte IP_ADDR_LAST_BYTE_MAX = (byte) 100;
private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_LOCAL =
InetAddresses.parseNumericAddress("192.0.2.1").getAddress();
private static final byte[] INITIAL_AVAILABLE_IP4_ADDR_REMOTE =
InetAddresses.parseNumericAddress("198.51.100.1").getAddress();
private static final byte[] NEXT_AVAILABLE_IP4_ADDR_LOCAL = INITIAL_AVAILABLE_IP4_ADDR_LOCAL;
private static final byte[] NEXT_AVAILABLE_IP4_ADDR_REMOTE = INITIAL_AVAILABLE_IP4_ADDR_REMOTE;
ParcelFileDescriptor mTunFd;
TestNetworkCallback mTunNetworkCallback;
Network mTunNetwork;
IkeTunUtils mTunUtils;
InetAddress mLocalAddress;
InetAddress mRemoteAddress;
Executor mUserCbExecutor;
TestIkeSessionCallback mIkeSessionCallback;
TestChildSessionCallback mFirstChildSessionCallback;
// This method is guaranteed to run in subclasses and will run before subclasses' @BeforeClass
// methods.
@BeforeClass
public static void setUpPermissionBeforeClass() throws Exception {
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity();
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
// Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
// a standard permission is insufficient. So we shell out the appop, to give us the
// right appop permissions.
setAppOp(OP_MANAGE_IPSEC_TUNNELS, true);
}
// This method is guaranteed to run in subclasses and will run after subclasses' @AfterClass
// methods.
@AfterClass
public static void tearDownPermissionAfterClass() throws Exception {
setAppOp(OP_MANAGE_IPSEC_TUNNELS, false);
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.dropShellPermissionIdentity();
}
@Before
public void setUp() throws Exception {
mLocalAddress = getNextAvailableIpv4AddressLocal();
mRemoteAddress = getNextAvailableIpv4AddressRemote();
setUpTestNetwork(mLocalAddress);
mUserCbExecutor = Executors.newSingleThreadExecutor();
mIkeSessionCallback = new TestIkeSessionCallback();
mFirstChildSessionCallback = new TestChildSessionCallback();
}
@After
public void tearDown() throws Exception {
tearDownTestNetwork();
resetNextAvailableAddress(NEXT_AVAILABLE_IP4_ADDR_LOCAL, INITIAL_AVAILABLE_IP4_ADDR_LOCAL);
resetNextAvailableAddress(
NEXT_AVAILABLE_IP4_ADDR_REMOTE, INITIAL_AVAILABLE_IP4_ADDR_REMOTE);
}
void setUpTestNetwork(InetAddress localAddr) throws Exception {
int prefixLen = localAddr instanceof Inet4Address ? IP4_PREFIX_LEN : IP4_PREFIX_LEN;
TestNetworkInterface testIface =
sTNM.createTunInterface(new LinkAddress[] {new LinkAddress(localAddr, prefixLen)});
mTunFd = testIface.getFileDescriptor();
mTunNetworkCallback =
TestNetworkUtils.setupAndGetTestNetwork(
sCM, sTNM, testIface.getInterfaceName(), new Binder());
mTunNetwork = mTunNetworkCallback.getNetworkBlocking();
mTunUtils = new IkeTunUtils(mTunFd);
}
void tearDownTestNetwork() throws Exception {
sCM.unregisterNetworkCallback(mTunNetworkCallback);
sTNM.teardownTestNetwork(mTunNetwork);
mTunFd.close();
}
private static void setAppOp(int appop, boolean allow) {
String opName = AppOpsManager.opToName(appop);
for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
String cmd =
String.format(
"appops set %s %s %s",
pkg, // Package name
opName, // Appop
(allow ? "allow" : "deny")); // Action
Log.d("IKE", "CTS setAppOp cmd " + cmd);
String result = SystemUtil.runShellCommand(cmd);
}
}
Inet4Address getNextAvailableIpv4AddressLocal() throws Exception {
return (Inet4Address)
getNextAvailableAddress(
NEXT_AVAILABLE_IP4_ADDR_LOCAL,
INITIAL_AVAILABLE_IP4_ADDR_LOCAL,
false /* isIp6 */);
}
Inet4Address getNextAvailableIpv4AddressRemote() throws Exception {
return (Inet4Address)
getNextAvailableAddress(
NEXT_AVAILABLE_IP4_ADDR_REMOTE,
INITIAL_AVAILABLE_IP4_ADDR_REMOTE,
false /* isIp6 */);
}
InetAddress getNextAvailableAddress(
byte[] nextAddressBytes, byte[] initialAddressBytes, boolean isIp6) throws Exception {
int addressLen = isIp6 ? IP6_ADDRESS_LEN : IP4_ADDRESS_LEN;
synchronized (nextAddressBytes) {
if (nextAddressBytes[addressLen - 1] == IP_ADDR_LAST_BYTE_MAX) {
resetNextAvailableAddress(nextAddressBytes, initialAddressBytes);
}
InetAddress address = InetAddress.getByAddress(nextAddressBytes);
nextAddressBytes[addressLen - 1]++;
return address;
}
}
private void resetNextAvailableAddress(byte[] nextAddressBytes, byte[] initialAddressBytes) {
synchronized (nextAddressBytes) {
System.arraycopy(
nextAddressBytes, 0, initialAddressBytes, 0, initialAddressBytes.length);
}
}
static class TestIkeSessionCallback implements IkeSessionCallback {
private CompletableFuture<IkeSessionConfiguration> mFutureIkeConfig =
new CompletableFuture<>();
private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
private CompletableFuture<IkeException> mFutureOnClosedException =
new CompletableFuture<>();
private int mOnErrorExceptionsCount = 0;
private ArrayTrackRecord<IkeProtocolException> mOnErrorExceptionsTrackRecord =
new ArrayTrackRecord<>();
@Override
public void onOpened(@NonNull IkeSessionConfiguration sessionConfiguration) {
mFutureIkeConfig.complete(sessionConfiguration);
}
@Override
public void onClosed() {
mFutureOnClosedCall.complete(true /* unused */);
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
mFutureOnClosedException.complete(exception);
}
@Override
public void onError(@NonNull IkeProtocolException exception) {
mOnErrorExceptionsTrackRecord.add(exception);
}
public IkeSessionConfiguration awaitIkeConfig() throws Exception {
return mFutureIkeConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public IkeException awaitOnClosedException() throws Exception {
return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public IkeProtocolException awaitNextOnErrorException() {
return mOnErrorExceptionsTrackRecord.poll(
(long) TIMEOUT_MS,
mOnErrorExceptionsCount++,
(transform) -> {
return true;
});
}
public void awaitOnClosed() throws Exception {
mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
static class TestChildSessionCallback implements ChildSessionCallback {
private CompletableFuture<ChildSessionConfiguration> mFutureChildConfig =
new CompletableFuture<>();
private CompletableFuture<Boolean> mFutureOnClosedCall = new CompletableFuture<>();
private CompletableFuture<IkeException> mFutureOnClosedException =
new CompletableFuture<>();
private int mCreatedIpSecTransformCount = 0;
private int mDeletedIpSecTransformCount = 0;
private ArrayTrackRecord<IpSecTransformCallRecord> mCreatedIpSecTransformsTrackRecord =
new ArrayTrackRecord<>();
private ArrayTrackRecord<IpSecTransformCallRecord> mDeletedIpSecTransformsTrackRecord =
new ArrayTrackRecord<>();
@Override
public void onOpened(@NonNull ChildSessionConfiguration sessionConfiguration) {
mFutureChildConfig.complete(sessionConfiguration);
}
@Override
public void onClosed() {
mFutureOnClosedCall.complete(true /* unused */);
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
mFutureOnClosedException.complete(exception);
}
@Override
public void onIpSecTransformCreated(@NonNull IpSecTransform ipSecTransform, int direction) {
mCreatedIpSecTransformsTrackRecord.add(
new IpSecTransformCallRecord(ipSecTransform, direction));
}
@Override
public void onIpSecTransformDeleted(@NonNull IpSecTransform ipSecTransform, int direction) {
mDeletedIpSecTransformsTrackRecord.add(
new IpSecTransformCallRecord(ipSecTransform, direction));
}
public ChildSessionConfiguration awaitChildConfig() throws Exception {
return mFutureChildConfig.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public IkeException awaitOnClosedException() throws Exception {
return mFutureOnClosedException.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public IpSecTransformCallRecord awaitNextCreatedIpSecTransform() {
return mCreatedIpSecTransformsTrackRecord.poll(
(long) TIMEOUT_MS,
mCreatedIpSecTransformCount++,
(transform) -> {
return true;
});
}
public IpSecTransformCallRecord awaitNextDeletedIpSecTransform() {
return mDeletedIpSecTransformsTrackRecord.poll(
(long) TIMEOUT_MS,
mDeletedIpSecTransformCount++,
(transform) -> {
return true;
});
}
public void awaitOnClosed() throws Exception {
mFutureOnClosedCall.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
/**
* This class represents a created or deleted IpSecTransfrom that is provided by
* ChildSessionCallback
*/
static class IpSecTransformCallRecord {
public final IpSecTransform ipSecTransform;
public final int direction;
IpSecTransformCallRecord(IpSecTransform ipSecTransform, @PolicyDirection int direction) {
this.ipSecTransform = ipSecTransform;
this.direction = direction;
}
}
// TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth
// TODO(b/148689509): Verify hostname based creation
}

View File

@@ -31,13 +31,15 @@ import java.util.Map;
/** Shared parameters and util methods for testing different components of IKE */
abstract class IkeTestBase {
private static final int MIN_PORT = 0;
private static final int MAX_PORT = 65535;
static final int MIN_PORT = 0;
static final int MAX_PORT = 65535;
private static final int INBOUND_TS_START_PORT = MIN_PORT;
private static final int INBOUND_TS_END_PORT = 65520;
private static final int OUTBOUND_TS_START_PORT = 16;
private static final int OUTBOUND_TS_END_PORT = MAX_PORT;
static final int IP4_ADDRESS_LEN = 4;
static final int IP6_ADDRESS_LEN = 16;
static final int IP4_PREFIX_LEN = 32;
static final int IP6_PREFIX_LEN = 64;

View File

@@ -0,0 +1,243 @@
/*
* Copyright (C) 2020 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 android.net.ipsec.ike.cts;
import static android.net.ipsec.ike.cts.PacketUtils.BytePayload;
import static android.net.ipsec.ike.cts.PacketUtils.IP4_HDRLEN;
import static android.net.ipsec.ike.cts.PacketUtils.IP6_HDRLEN;
import static android.net.ipsec.ike.cts.PacketUtils.Ip4Header;
import static android.net.ipsec.ike.cts.PacketUtils.Ip6Header;
import static android.net.ipsec.ike.cts.PacketUtils.IpHeader;
import static android.net.ipsec.ike.cts.PacketUtils.Payload;
import static android.net.ipsec.ike.cts.PacketUtils.UDP_HDRLEN;
import static android.net.ipsec.ike.cts.PacketUtils.UdpHeader;
import static android.system.OsConstants.IPPROTO_UDP;
import static org.junit.Assert.fail;
import android.os.ParcelFileDescriptor;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class IkeTunUtils extends TunUtils {
private static final int PORT_LEN = 2;
private static final int NON_ESP_MARKER_LEN = 4;
private static final byte[] NON_ESP_MARKER = new byte[NON_ESP_MARKER_LEN];
private static final int IKE_HEADER_LEN = 28;
private static final int IKE_INIT_SPI_OFFSET = 0;
private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
private static final int IKE_MSG_ID_OFFSET = 20;
public IkeTunUtils(ParcelFileDescriptor tunFd) {
super(tunFd);
}
/**
* Await the expected IKE request and inject an IKE response.
*
* @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
*/
public byte[] awaitReqAndInjectResp(
long expectedInitIkeSpi, int expectedMsgId, boolean expectedUseEncap, byte[] respIkePkt)
throws Exception {
byte[] request =
awaitIkePacket(
expectedInitIkeSpi,
expectedMsgId,
false /* expectedResp */,
expectedUseEncap);
// Build response header by flipping address and port
InetAddress srcAddr = getAddress(request, false /* shouldGetSource */);
InetAddress dstAddr = getAddress(request, true /* shouldGetSource */);
int srcPort = getPort(request, false /* shouldGetSource */);
int dstPort = getPort(request, true /* shouldGetSource */);
byte[] response =
buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, expectedUseEncap, respIkePkt);
injectPacket(response);
return request;
}
private byte[] awaitIkePacket(
long expectedInitIkeSpi,
int expectedMsgId,
boolean expectedResp,
boolean expectedUseEncap)
throws Exception {
long endTime = System.currentTimeMillis() + TIMEOUT;
int startIndex = 0;
synchronized (mPackets) {
while (System.currentTimeMillis() < endTime) {
byte[] ikePkt =
getFirstMatchingPacket(
(pkt) -> {
return isIke(
pkt,
expectedInitIkeSpi,
expectedMsgId,
expectedResp,
expectedUseEncap);
},
startIndex);
if (ikePkt != null) {
return ikePkt; // We've found the packet we're looking for.
}
startIndex = mPackets.size();
// Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
long waitTimeout = endTime - System.currentTimeMillis();
if (waitTimeout > 0) {
mPackets.wait(waitTimeout);
}
}
String direction = expectedResp ? "response" : "request";
fail(
"No such IKE "
+ direction
+ " found with Initiator SPI "
+ expectedInitIkeSpi
+ " and message ID "
+ expectedMsgId);
}
return null;
}
private static boolean isIke(
byte[] pkt,
long expectedInitIkeSpi,
int expectedMsgId,
boolean expectedResp,
boolean expectedUseEncap) {
int ipProtocolOffset = 0;
int ikeOffset = 0;
if (isIpv6(pkt)) {
// IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap.
ipProtocolOffset = IP6_PROTO_OFFSET;
ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
} else {
// Use default IPv4 header length (assuming no options)
ipProtocolOffset = IP4_PROTO_OFFSET;
ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
if (expectedUseEncap) {
if (hasNonEspMarker(pkt)) {
ikeOffset += NON_ESP_MARKER_LEN;
} else {
return false;
}
}
}
return pkt[ipProtocolOffset] == IPPROTO_UDP
&& areSpiAndMsgIdEqual(
pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp);
}
private static boolean hasNonEspMarker(byte[] pkt) {
ByteBuffer buffer = ByteBuffer.wrap(pkt);
int ikeOffset = IP4_HDRLEN + UDP_HDRLEN;
if (buffer.remaining() < ikeOffset) return false;
buffer.get(new byte[ikeOffset]); // Skip IP and UDP header
byte[] nonEspMarker = new byte[NON_ESP_MARKER_LEN];
if (buffer.remaining() < NON_ESP_MARKER_LEN) return false;
buffer.get(nonEspMarker);
return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
}
private static boolean areSpiAndMsgIdEqual(
byte[] pkt,
int ikeOffset,
long expectedIkeInitSpi,
int expectedMsgId,
boolean expectedResp) {
if (pkt.length <= ikeOffset + IKE_HEADER_LEN) return false;
ByteBuffer buffer = ByteBuffer.wrap(pkt);
buffer.get(new byte[ikeOffset]); // Skip IP, UDP header (and NON_ESP_MARKER)
// Check message ID.
buffer.get(new byte[IKE_MSG_ID_OFFSET]);
int msgId = buffer.getInt();
return expectedMsgId == msgId;
// TODO: Check SPI and packet direction
}
private static InetAddress getAddress(byte[] pkt, boolean shouldGetSource) throws Exception {
int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
int ipOffset = shouldGetSource ? srcIpOffset : srcIpOffset + ipLen;
ByteBuffer buffer = ByteBuffer.wrap(pkt);
buffer.get(new byte[ipOffset]);
byte[] ipAddrBytes = new byte[ipLen];
buffer.get(ipAddrBytes);
return InetAddress.getByAddress(ipAddrBytes);
}
private static int getPort(byte[] pkt, boolean shouldGetSource) {
ByteBuffer buffer = ByteBuffer.wrap(pkt);
int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
int portOffset = shouldGetSource ? srcPortOffset : srcPortOffset + PORT_LEN;
buffer.get(new byte[portOffset]);
return Short.toUnsignedInt(buffer.getShort());
}
private static byte[] buildIkePacket(
InetAddress srcAddr,
InetAddress dstAddr,
int srcPort,
int dstPort,
boolean useEncap,
byte[] ikePacket)
throws Exception {
if (useEncap) {
ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER_LEN + ikePacket.length);
buffer.put(NON_ESP_MARKER);
buffer.put(ikePacket);
ikePacket = buffer.array();
}
UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(ikePacket));
IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
return ipPkt.getPacketBytes();
}
private static IpHeader getIpHeader(
int protocol, InetAddress src, InetAddress dst, Payload payload) {
if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
throw new IllegalArgumentException("Invalid src/dst address combination");
}
if (src instanceof Inet6Address) {
return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
} else {
return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
}
}
}

View File

@@ -47,18 +47,18 @@ public class TunUtils {
private static final String TAG = TunUtils.class.getSimpleName();
private static final int DATA_BUFFER_LEN = 4096;
private static final int TIMEOUT = 100;
static final int TIMEOUT = 100;
private static final int IP4_PROTO_OFFSET = 9;
private static final int IP6_PROTO_OFFSET = 6;
static final int IP4_PROTO_OFFSET = 9;
static final int IP6_PROTO_OFFSET = 6;
private static final int IP4_ADDR_OFFSET = 12;
private static final int IP4_ADDR_LEN = 4;
private static final int IP6_ADDR_OFFSET = 8;
private static final int IP6_ADDR_LEN = 16;
static final int IP4_ADDR_OFFSET = 12;
static final int IP4_ADDR_LEN = 4;
static final int IP6_ADDR_OFFSET = 8;
static final int IP6_ADDR_LEN = 16;
final List<byte[]> mPackets = new ArrayList<>();
private final ParcelFileDescriptor mTunFd;
private final List<byte[]> mPackets = new ArrayList<>();
private final Thread mReaderThread;
public TunUtils(ParcelFileDescriptor tunFd) {
@@ -107,7 +107,7 @@ public class TunUtils {
return Arrays.copyOf(inBytes, bytesRead);
}
private byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
synchronized (mPackets) {
for (int i = startIndex; i < mPackets.size(); i++) {
byte[] pkt = mPackets.get(i);
@@ -198,7 +198,7 @@ public class TunUtils {
}
}
private static boolean isIpv6(byte[] pkt) {
static boolean isIpv6(byte[] pkt) {
// First nibble shows IP version. 0x60 for IPv6
return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
}