From 1e024e26cf5adb46ead8f3b6737cafb03b9cac72 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Thu, 28 May 2020 00:53:46 +0000 Subject: [PATCH] Test IKE Session setup with digital-signature-based Auth This commit adds a test for setting up IKE Session with digital-signature-based authentication. This test also verifies sending and receiving IKE fragments Bug: 155821007 Test: atest CtsIkeTestCases:IkeSessionDigitalSignatureTest Change-Id: I5829dfa955c47c0810760b7bf97372031e740f1e Merged-In: I5829dfa955c47c0810760b7bf97372031e740f1e (cherry picked from commit 3fde403c39e048e82223256ec7ca5c3ac7c93751) --- .../cts/IkeSessionDigitalSignatureTest.java | 210 ++++++++++++++++++ .../net/ipsec/ike/cts/IkeSessionPskTest.java | 20 +- .../net/ipsec/ike/cts/IkeSessionTestBase.java | 50 +++-- .../net/ipsec/ike/cts/IkeTunUtils.java | 166 ++++++++++---- .../android/net/ipsec/ike/cts/TunUtils.java | 2 +- 5 files changed, 383 insertions(+), 65 deletions(-) create mode 100644 tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java new file mode 100644 index 0000000000..ebf8a41f00 --- /dev/null +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionDigitalSignatureTest.java @@ -0,0 +1,210 @@ +/* + * 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.net.InetAddresses; +import android.net.LinkAddress; +import android.net.ipsec.ike.IkeDerAsn1DnIdentification; +import android.net.ipsec.ike.IkeSession; +import android.net.ipsec.ike.IkeSessionParams; +import android.net.ipsec.ike.IkeTrafficSelector; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.net.ipsec.ike.testutils.CertUtils; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.security.auth.x500.X500Principal; + +/** + * Explicitly test setting up transport mode Child SA so that devices do not have + * FEATURE_IPSEC_TUNNELS will be test covered. Tunnel mode Child SA setup has been tested in + * IkeSessionPskTest and authentication method is orthogonal to Child mode. + */ +@RunWith(AndroidJUnit4.class) +public class IkeSessionDigitalSignatureTest extends IkeSessionTestBase { + private static final int EXPECTED_AUTH_REQ_FRAG_COUNT = 3; + + private static final String IKE_INIT_RESP = + "46B8ECA1E0D72A18BF3FA1C2CB1EE86F21202220000000000000015022000030" + + "0000002C010100040300000C0100000C800E0100030000080300000503000008" + + "0200000400000008040000022800008800020000328451C8A976CE69E407966A" + + "50D7320C4197A15A07267CE1B16BAFF9BDBBDEC1FDCDAAF7175ADF9AA8DB55DB" + + "2D70C012D01D914C4EDEF6E8B226868EA1D01B2ED0C4C5C86E6BFE566010EC0C" + + "33BA1C93666430B88BDA0470D82CC4F4416F49E3E361E3017C9F27811A66718B" + + "389E1800915D776D59AA528A7E1D1B7815D35144290000249FE8FABE7F43D917" + + "CE370DE2FD9C22BBC082951AC26C1BA26DE795470F2C25BC2900001C00004004" + + "AE388EC86D6D1A470D44142D01AB2E85A7AC14182900001C0000400544A235A4" + + "171C884286B170F48FFC181DB428D87D290000080000402E290000100000402F" + + "00020003000400050000000800004014"; + private static final String IKE_AUTH_RESP_FRAG_1 = + "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000004E0240004C4" + + "00010002DF6750A2D1D5675006F9F6230BB886FFD20CFB973FD04963CFD7A528" + + "560598C58CC44178B2FCBBBBB271387AC81A664B7E7F1055B912F8C686E287C9" + + "D31684C66339151AB86DA3CF1DA664052FA97687634558A1E9E6B37E16A86BD1" + + "68D76DA5E2E1E0B7E98EB662D80D542307015D2BF134EBBBE425D6954FE8C2C4" + + "D31D16C16AA0521C3C481F873ECF25BB8B05AC6083775C1821CAAB1E35A3955D" + + "85ACC599574142E1DD5B262D6E5365CBF6EBE92FFCC16BC29EC3239456F3B202" + + "492551C0F6D752ADCCA56D506D50CC8809EF6BC56EAD005586F7168F76445FD3" + + "1366CC62D32C0C19B28210B8F813F97CD6A447C3857EFD6EC483DDA8ACD9870E" + + "5A21B9C66F0FA44496C0C3D05E8859A1A4CFC88155D0C411BABC13033DD41FA4" + + "AF08CE7734A146687F374F95634D1F26843203CA1FFD05CA3EB150CEA02FBF14" + + "712B7A1C9BC7616A086E7FCA059E7D64EFF98DB895B32F8F7002762AF7D12F23" + + "31E9DD25174C4CE273E5392BBB48F50B7A3E0187181216265F6A4FC7B91BE0AB" + + "C601A580149D4B07411AE99DDB1944B977E86ADC9746605C60A92B569EEFAFFC" + + "3A888D187B75D8F13249689FC28EBCD62B5E03AF171F3A561F0DEA3B1A75F531" + + "971157DCE1E7BC6E7789FF3E8156015BC9C521EFE48996B41471D33BF09864E4" + + "2436E8D7EB6218CDE7716DA754A924B123A63E25585BF27F4AC043A0C4AECE38" + + "BB59DD62F5C0EC657206A76CED1BD26262237DA1CA6815435992A825758DEBEC" + + "DDF598A22B8242AC4E34E70704DBA7B7B73DC3E067C1C98764F8791F84C99156" + + "947D1FFC875F36FCE24B89369C1B5BF1D4C999DCA28E72A528D0E0163C66C067" + + "E71B5E0025C13DA93313942F9EDA230B3ADC254821A4CB1A5DC9D0C5F4DC4E8E" + + "CE46B7B8C72D3C5923C9B30DF1EF7B4EDEDA8BD05C86CA0162AE1BF8F277878E" + + "607401BAA8F06E3EA873FA4C137324C4E0699277CDF649FE7F0F01945EE25FA7" + + "0E4A89737E58185B11B4CB52FD5B0497D3E3CD1CEE7B1FBB3E969DB6F4C324A1" + + "32DC6A0EA21F41332435FD99140C286F8ABBBA926953ADBEED17D30AAD953909" + + "1347EF6D87163D6B1FF32D8B11FFB2E69FAEE7FE913D3826FBA7F9D11E0E3C57" + + "27625B37D213710B5DD8965DAEFD3F491E8C029E2BF361039949BADEC31D60AC" + + "355F26EE41339C03CC9D9B01C3C7F288F0E9D6DFEE78231BDA9AC10FED135913" + + "2836B1A17CE060742B7E5B738A7177CCD59F70337BA251409C377A0FA5333204" + + "D8622BA8C06DE0BEF4F32B6D4D77BE9DE977445D8A2A08C5C38341CB7974FBFB" + + "22C8F983A7D6CEF068DDB2281E6673453521C831C1826861005AE5F37649BC64" + + "0A6360B23284861441A440F1C5AADE1AB53CA63DB17F4C314D493C4C44DE5F20" + + "75E084D080F92791F30BDD88373D50AB5A07BC72B0E7FFFA593103964E55603E" + + "F7FEB7CA0762A1A7B86B6CCAD88CD6CBC7C6935D21F5F06B2700588A2530E619" + + "DA1648AC809F3DDF56ACE5951737568FFEC7E2AB1AA0AE01B03A7F5A29CE73C0" + + "5D2801B17CAAD0121082E9952FAB16BA1C386336C62D4CF3A5019CF61609433E" + + "1C083237D47C4CF575097F7BF9000EF6B6C497A44E6480154A35669AD276BF05" + + "6CC730B4E5962B6AF96CC6D236AE85CEFDA6877173F72D2F614F6696D1F9DF07" + + "E107758B0978F69BC9DBE0CCBF252C40A3FDF7CE9104D3344F7B73593CCD73E0"; + private static final String IKE_AUTH_RESP_FRAG_2 = + "46B8ECA1E0D72A18BF3FA1C2CB1EE86F3520232000000001000000F0000000D4" + + "00020002155211EA41B37BC5F20568A6AE57038EEE208F94F9B444004F1EF391" + + "2CABFCF857B9CD95FAAA9489ED10A3F5C93510820E22E23FC55ED8049E067D72" + + "3645C00E1E08611916CE72D7F0A84123B63A8F3B9E78DBBE39967B7BB074AF4D" + + "BF2178D991EDBDD01908A14A266D09236DB963B14AC33D894F0F83A580209EFD" + + "61875BB56273AA336C22D6A4D890B93E0D42435667830CC32E4F608500E18569" + + "3E6C1D88C0B5AE427333C86468E3474DAA4D1506AAB2A4021309A33DD759D0D0" + + "A8C98BF7FBEA8109361A9F194D0FD756"; + private static final String DELETE_IKE_RESP = + "46B8ECA1E0D72A18BF3FA1C2CB1EE86F2E202520000000020000004C00000030" + + "342842D8DA37C8EFB92ED37C4FBB23CBDC90445137D6A0AF489F9F03641DBA9D" + + "02F6F59FD8A7A78C7261CEB8"; + + // Using IPv4 for transport mode Child SA. IPv6 is currently infeasible because the IKE server + // that generates the test vectors is running in an IPv4 only network. + private static final IkeTrafficSelector TRANSPORT_MODE_INBOUND_TS = + new IkeTrafficSelector( + MIN_PORT, + MAX_PORT, + InetAddresses.parseNumericAddress("172.58.35.103"), + InetAddresses.parseNumericAddress("172.58.35.103")); + + // TODO(b/157510502): Add test for IKE Session setup in IPv6 network + + private static final String LOCAL_ID_ASN1_DN = + "CN=client.test.ike.android.net, O=Android, C=US"; + private static final String REMOTE_ID_ASN1_DN = + "CN=server.test.ike.android.net, O=Android, C=US"; + + private static X509Certificate sServerCaCert; + private static X509Certificate sClientEndCert; + private static X509Certificate sClientIntermediateCaCertOne; + private static X509Certificate sClientIntermediateCaCertTwo; + private static RSAPrivateKey sClientPrivateKey; + + @BeforeClass + public static void setUpCertsBeforeClass() throws Exception { + sServerCaCert = CertUtils.createCertFromPemFile("server-a-self-signed-ca.pem"); + sClientEndCert = CertUtils.createCertFromPemFile("client-a-end-cert.pem"); + sClientIntermediateCaCertOne = + CertUtils.createCertFromPemFile("client-a-intermediate-ca-one.pem"); + sClientIntermediateCaCertTwo = + CertUtils.createCertFromPemFile("client-a-intermediate-ca-two.pem"); + sClientPrivateKey = CertUtils.createRsaPrivateKeyFromKeyFile("client-a-private-key.key"); + } + + private IkeSession openIkeSessionWithRemoteAddress(InetAddress remoteAddress) { + IkeSessionParams ikeParams = + new IkeSessionParams.Builder(sContext) + .setNetwork(mTunNetwork) + .setServerHostname(remoteAddress.getHostAddress()) + .addSaProposal(SaProposalTest.buildIkeSaProposalWithNormalModeCipher()) + .addSaProposal(SaProposalTest.buildIkeSaProposalWithCombinedModeCipher()) + .setLocalIdentification( + new IkeDerAsn1DnIdentification(new X500Principal(LOCAL_ID_ASN1_DN))) + .setRemoteIdentification( + new IkeDerAsn1DnIdentification( + new X500Principal(REMOTE_ID_ASN1_DN))) + .setAuthDigitalSignature( + sServerCaCert, + sClientEndCert, + Arrays.asList( + sClientIntermediateCaCertOne, sClientIntermediateCaCertTwo), + sClientPrivateKey) + .build(); + + return new IkeSession( + sContext, + ikeParams, + buildTransportModeChildParamsWithTs( + TRANSPORT_MODE_INBOUND_TS, TRANSPORT_MODE_OUTBOUND_TS), + mUserCbExecutor, + mIkeSessionCallback, + mFirstChildSessionCallback); + } + + @Test + public void testIkeSessionSetupAndChildSessionSetupWithTransportMode() throws Exception { + // Open IKE Session + IkeSession ikeSession = openIkeSessionWithRemoteAddress(mRemoteAddress); + performSetupIkeAndFirstChildBlocking( + IKE_INIT_RESP, + EXPECTED_AUTH_REQ_FRAG_COUNT /* expectedReqPktCnt */, + IKE_AUTH_RESP_FRAG_1, + IKE_AUTH_RESP_FRAG_2); + + // IKE INIT and IKE AUTH takes two exchanges. Message ID starts from 2 + int expectedMsgId = 2; + + verifyIkeSessionSetupBlocking(); + verifyChildSessionSetupBlocking( + mFirstChildSessionCallback, + Arrays.asList(TRANSPORT_MODE_INBOUND_TS), + Arrays.asList(TRANSPORT_MODE_OUTBOUND_TS), + new ArrayList()); + IpSecTransformCallRecord firstTransformRecordA = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + IpSecTransformCallRecord firstTransformRecordB = + mFirstChildSessionCallback.awaitNextCreatedIpSecTransform(); + verifyCreateIpSecTransformPair(firstTransformRecordA, firstTransformRecordB); + + // Close IKE Session + ikeSession.close(); + performCloseIkeBlocking(expectedMsgId++, DELETE_IKE_RESP); + verifyCloseIkeAndChildBlocking(firstTransformRecordA, firstTransformRecordB); + } +} diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java index 661457ff5e..ef865afe9a 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionPskTest.java @@ -16,6 +16,7 @@ package android.net.ipsec.ike.cts; +import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS; import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN; import static org.junit.Assert.assertArrayEquals; @@ -33,6 +34,8 @@ import android.platform.test.annotations.AppModeFull; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -102,6 +105,21 @@ public class IkeSessionPskTest extends IkeSessionTestBase { mFirstChildSessionCallback); } + @BeforeClass + public static void setUpTunnelPermissionBeforeClass() throws Exception { + // 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 tearDownTunnelPermissionAfterClass() throws Exception { + setAppOp(OP_MANAGE_IPSEC_TUNNELS, false); + } + @Test public void testIkeSessionSetupAndChildSessionSetupWithTunnelMode() throws Exception { if (!hasTunnelsFeature()) return; @@ -203,6 +221,4 @@ public class IkeSessionPskTest extends IkeSessionTestBase { } // TODO(b/155821007): Verify rekey process and handling IKE_AUTH failure - - // TODO(b/155821007): Test creating transport mode Child SA } diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java index ade9813a81..5b545c4394 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeSessionTestBase.java @@ -15,7 +15,6 @@ package android.net.ipsec.ike.cts; -import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS; import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_FRAGMENTATION; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; @@ -43,6 +42,7 @@ import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionConfiguration; import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.ipsec.ike.IkeTrafficSelector; +import android.net.ipsec.ike.TransportModeChildSessionParams; import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.cts.TestNetworkUtils.TestNetworkCallback; import android.net.ipsec.ike.exceptions.IkeException; @@ -103,6 +103,14 @@ abstract class IkeSessionTestBase extends IkeTestBase { MIN_PORT, MAX_PORT, EXPECTED_INTERNAL_ADDR, EXPECTED_INTERNAL_ADDR); static final IkeTrafficSelector TUNNEL_MODE_OUTBOUND_TS = DEFAULT_V4_TS; + // This value is align with the test vectors hex that are generated in an IPv4 environment + static final IkeTrafficSelector TRANSPORT_MODE_OUTBOUND_TS = + new IkeTrafficSelector( + MIN_PORT, + MAX_PORT, + InetAddresses.parseNumericAddress("10.138.0.2"), + InetAddresses.parseNumericAddress("10.138.0.2")); + static final long IKE_DETERMINISTIC_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16); // Static state to reduce setup/teardown @@ -142,19 +150,12 @@ abstract class IkeSessionTestBase extends IkeTestBase { .getUiAutomation() .adoptShellPermissionIdentity(); sTNM = sContext.getSystemService(TestNetworkManager.class); - - // 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(); @@ -197,7 +198,7 @@ abstract class IkeSessionTestBase extends IkeTestBase { mTunFd.close(); } - private static void setAppOp(int appop, boolean allow) { + static void setAppOp(int appop, boolean allow) { String opName = AppOpsManager.opToName(appop); for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) { String cmd = @@ -249,6 +250,16 @@ abstract class IkeSessionTestBase extends IkeTestBase { } } + TransportModeChildSessionParams buildTransportModeChildParamsWithTs( + IkeTrafficSelector inboundTs, IkeTrafficSelector outboundTs) { + return new TransportModeChildSessionParams.Builder() + .addSaProposal(SaProposalTest.buildChildSaProposalWithCombinedModeCipher()) + .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher()) + .addInboundTrafficSelectors(inboundTs) + .addOutboundTrafficSelectors(outboundTs) + .build(); + } + TunnelModeChildSessionParams buildTunnelModeChildSessionParams() { return new TunnelModeChildSessionParams.Builder() .addSaProposal(SaProposalTest.buildChildSaProposalWithNormalModeCipher()) @@ -258,7 +269,14 @@ abstract class IkeSessionTestBase extends IkeTestBase { .build(); } - void performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String ikeAuthRespHex) + void performSetupIkeAndFirstChildBlocking(String ikeInitRespHex, String... ikeAuthRespHexes) + throws Exception { + performSetupIkeAndFirstChildBlocking( + ikeInitRespHex, 1 /* expectedAuthReqPktCnt */, ikeAuthRespHexes); + } + + void performSetupIkeAndFirstChildBlocking( + String ikeInitRespHex, int expectedAuthReqPktCnt, String... ikeAuthRespHexes) throws Exception { mTunUtils.awaitReqAndInjectResp( IKE_DETERMINISTIC_INITIATOR_SPI, @@ -270,14 +288,8 @@ abstract class IkeSessionTestBase extends IkeTestBase { IKE_DETERMINISTIC_INITIATOR_SPI, 1 /* expectedMsgId */, true /* expectedUseEncap */, - ikeAuthRespHex); - } - - void performSetupIkeAndFirstChildBlocking( - String ikeInitRespHex, int expectedAuthReqPktCnt, String... ikeAuthRespPktHex) - throws Exception { - // TODO: Implemented in followup CL (aosp/1308675) to support awaiting multiple IKE AUTH - // request fragments and injecting multiple IKE AUTH response fragments + expectedAuthReqPktCnt, + ikeAuthRespHexes); } void performCloseIkeBlocking(int expectedMsgId, String deleteIkeRespHex) throws Exception { @@ -522,7 +534,7 @@ abstract class IkeSessionTestBase extends IkeTestBase { return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS); } - // TODO(b/148689509): Verify IKE Session setup using EAP and digital-signature-based auth + // TODO(b/148689509): Verify IKE Session setup using EAP // TODO(b/148689509): Verify hostname based creation } diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java index 2bff63a753..a9daf6adc3 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/IkeTunUtils.java @@ -36,7 +36,9 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; public class IkeTunUtils extends TunUtils { @@ -45,10 +47,13 @@ public class IkeTunUtils extends TunUtils { 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_FIRST_PAYLOAD_OFFSET = 16; private static final int IKE_IS_RESP_BYTE_OFFSET = 19; private static final int IKE_MSG_ID_OFFSET = 20; + private static final int IKE_HEADER_LEN = 28; + private static final int IKE_FRAG_NUM_OFFSET = 32; + private static final int IKE_PAYLOAD_TYPE_SKF = 53; public IkeTunUtils(ParcelFileDescriptor tunFd) { super(tunFd); @@ -65,16 +70,65 @@ public class IkeTunUtils extends TunUtils { boolean expectedUseEncap, String ikeRespDataHex) throws Exception { - byte[] request = - awaitIkePacket( - (pkt) -> { - return isExpectedIkePkt( - pkt, - expectedInitIkeSpi, - expectedMsgId, - false /* expectedResp */, - expectedUseEncap); - }); + return awaitReqAndInjectResp( + expectedInitIkeSpi, + expectedMsgId, + expectedUseEncap, + 1 /* expectedReqPktCnt */, + ikeRespDataHex) + .get(0); + } + + /** + * Await the expected IKE request (or the list of IKE request fragments) and inject an IKE + * response (or a list of response fragments) + * + * @param ikeRespDataHexes IKE response hex (or a list of response fragments) without IP/UDP + * headers or NON ESP MARKER. + */ + public List awaitReqAndInjectResp( + long expectedInitIkeSpi, + int expectedMsgId, + boolean expectedUseEncap, + int expectedReqPktCnt, + String... ikeRespDataHexes) + throws Exception { + List reqList = new ArrayList<>(expectedReqPktCnt); + if (expectedReqPktCnt == 1) { + // Expecting one complete IKE packet + byte[] req = + awaitIkePacket( + (pkt) -> { + return isExpectedIkePkt( + pkt, + expectedInitIkeSpi, + expectedMsgId, + false /* expectedResp */, + expectedUseEncap); + }); + reqList.add(req); + } else { + // Expecting "expectedReqPktCnt" number of request fragments + for (int i = 0; i < expectedReqPktCnt; i++) { + // IKE Fragment number always starts from 1 + int expectedFragNum = i + 1; + byte[] req = + awaitIkePacket( + (pkt) -> { + return isExpectedIkeFragPkt( + pkt, + expectedInitIkeSpi, + expectedMsgId, + false /* expectedResp */, + expectedUseEncap, + expectedFragNum); + }); + reqList.add(req); + } + } + + // All request fragments have the same addresses and ports + byte[] request = reqList.get(0); // Build response header by flipping address and port InetAddress srcAddr = getAddress(request, false /* shouldGetSource */); @@ -82,20 +136,20 @@ public class IkeTunUtils extends TunUtils { int srcPort = getPort(request, false /* shouldGetSource */); int dstPort = getPort(request, true /* shouldGetSource */); - byte[] response = - buildIkePacket( - srcAddr, - dstAddr, - srcPort, - dstPort, - expectedUseEncap, - hexStringToByteArray(ikeRespDataHex)); - injectPacket(response); - return request; - } + for (String hex : ikeRespDataHexes) { + byte[] response = + buildIkePacket( + srcAddr, + dstAddr, + srcPort, + dstPort, + expectedUseEncap, + hexStringToByteArray(hex)); + injectPacket(response); + } - // TODO: Implemented in followup CL (aosp/1308675) to support awaiting multiple - // request fragments and injecting multiple response fragments + return reqList; + } private byte[] awaitIkePacket(Predicate pktVerifier) throws Exception { long endTime = System.currentTimeMillis() + TIMEOUT; @@ -129,31 +183,38 @@ public class IkeTunUtils extends TunUtils { 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; - } - } - } + int ipProtocolOffset = isIpv6(pkt) ? IP6_PROTO_OFFSET : IP4_PROTO_OFFSET; + int ikeOffset = getIkeOffset(pkt, expectedUseEncap); return pkt[ipProtocolOffset] == IPPROTO_UDP + && expectedUseEncap == hasNonEspMarker(pkt) && isExpectedSpiAndMsgId( pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId, expectedResp); } + private static boolean isExpectedIkeFragPkt( + byte[] pkt, + long expectedInitIkeSpi, + int expectedMsgId, + boolean expectedResp, + boolean expectedUseEncap, + int expectedFragNum) { + return isExpectedIkePkt( + pkt, expectedInitIkeSpi, expectedMsgId, expectedResp, expectedUseEncap) + && isExpectedFragNum(pkt, getIkeOffset(pkt, expectedUseEncap), expectedFragNum); + } + + private static int getIkeOffset(byte[] pkt, boolean useEncap) { + if (isIpv6(pkt)) { + // IPv6 UDP expectedUseEncap not supported by kernels; assume non-expectedUseEncap. + return IP6_HDRLEN + UDP_HDRLEN; + } else { + // Use default IPv4 header length (assuming no options) + int ikeOffset = IP4_HDRLEN + UDP_HDRLEN; + return useEncap ? ikeOffset + NON_ESP_MARKER_LEN : ikeOffset; + } + } + private static boolean hasNonEspMarker(byte[] pkt) { ByteBuffer buffer = ByteBuffer.wrap(pkt); int ikeOffset = IP4_HDRLEN + UDP_HDRLEN; @@ -186,6 +247,25 @@ public class IkeTunUtils extends TunUtils { // TODO: Check SPI and packet direction } + private static boolean isExpectedFragNum(byte[] pkt, int ikeOffset, int expectedFragNum) { + ByteBuffer buffer = ByteBuffer.wrap(pkt); + buffer.get(new byte[ikeOffset]); + buffer.mark(); // Mark this position so that later we can reset back here + + // Check if it is a fragment packet + buffer.get(new byte[IKE_FIRST_PAYLOAD_OFFSET]); + int firstPayload = Byte.toUnsignedInt(buffer.get()); + if (firstPayload != IKE_PAYLOAD_TYPE_SKF) { + return false; + } + + // Check fragment number + buffer.reset(); + buffer.get(new byte[IKE_FRAG_NUM_OFFSET]); + int fragNum = Short.toUnsignedInt(buffer.getShort()); + return expectedFragNum == fragNum; + } + 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; diff --git a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java index cb1d8269d7..5539dbca23 100644 --- a/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java +++ b/tests/cts/net/ipsec/src/android/net/ipsec/ike/cts/TunUtils.java @@ -47,7 +47,7 @@ public class TunUtils { private static final String TAG = TunUtils.class.getSimpleName(); private static final int DATA_BUFFER_LEN = 4096; - static final int TIMEOUT = 100; + static final int TIMEOUT = 500; static final int IP4_PROTO_OFFSET = 9; static final int IP6_PROTO_OFFSET = 6;