diff options
| author | Remi NGUYEN VAN <reminv@google.com> | 2021-05-11 13:37:06 +0000 |
|---|---|---|
| committer | Remi NGUYEN VAN <reminv@google.com> | 2021-05-13 08:30:33 +0000 |
| commit | 5ed250d90ad25a5eaee6191ae041c9e600896ad6 (patch) | |
| tree | a2e5825314542e2c2d3e490af0bc7a95f4c7698e /tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java | |
| parent | 87deb0b38ad89ad55d1f75a8c13a1d9c0750806c (diff) | |
Move net unit tests to packages/Connectivity
Move the tests together with packages/Connectivity code, so both can be
moved to packages/modules/Connectivity together.
Also reorganize unit tests in a unit/ directory, as other tests
(integration/, common/ etc.) have been added in tests/net since they
were created. This makes the directory structure consistent.
Test: atest FrameworksNetTests
Bug: 187814163
Merged-In: I254ffd1c08ec058d594b4ea55cbae5505f8497cc
Change-Id: I254ffd1c08ec058d594b4ea55cbae5505f8497cc
Diffstat (limited to 'tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java')
| -rw-r--r-- | tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java | 1004 |
1 files changed, 1004 insertions, 0 deletions
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java new file mode 100644 index 0000000000..cf2c9c783a --- /dev/null +++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java @@ -0,0 +1,1004 @@ +/* + * Copyright (C) 2017 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.server; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.INetd.IF_STATE_DOWN; +import static android.net.INetd.IF_STATE_UP; +import static android.net.IpSecManager.DIRECTION_FWD; +import static android.net.IpSecManager.DIRECTION_IN; +import static android.net.IpSecManager.DIRECTION_OUT; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; +import android.net.IpSecAlgorithm; +import android.net.IpSecConfig; +import android.net.IpSecManager; +import android.net.IpSecSpiResponse; +import android.net.IpSecTransform; +import android.net.IpSecTransformResponse; +import android.net.IpSecTunnelInterfaceResponse; +import android.net.IpSecUdpEncapResponse; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.system.Os; +import android.test.mock.MockContext; +import android.util.ArraySet; + +import androidx.test.filters.SmallTest; + +import com.android.server.IpSecService.TunnelInterfaceRecord; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.net.Inet4Address; +import java.net.Socket; +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +/** Unit tests for {@link IpSecService}. */ +@SmallTest +@RunWith(Parameterized.class) +public class IpSecServiceParameterizedTest { + + private static final int TEST_SPI = 0xD1201D; + + private final String mSourceAddr; + private final String mDestinationAddr; + private final LinkAddress mLocalInnerAddress; + private final int mFamily; + + private static final int[] ADDRESS_FAMILIES = + new int[] {AF_INET, AF_INET6}; + + @Parameterized.Parameters + public static Collection ipSecConfigs() { + return Arrays.asList( + new Object[][] { + {"1.2.3.4", "8.8.4.4", "10.0.1.1/24", AF_INET}, + {"2601::2", "2601::10", "2001:db8::1/64", AF_INET6} + }); + } + + private static final byte[] AEAD_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x73, 0x61, 0x6C, 0x74 + }; + private static final byte[] CRYPT_KEY = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + private static final byte[] AUTH_KEY = { + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, + 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F + }; + + AppOpsManager mMockAppOps = mock(AppOpsManager.class); + ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class); + + TestContext mTestContext = new TestContext(); + + private class TestContext extends MockContext { + private Set<String> mAllowedPermissions = new ArraySet<>(Arrays.asList( + android.Manifest.permission.MANAGE_IPSEC_TUNNELS, + android.Manifest.permission.NETWORK_STACK, + PERMISSION_MAINLINE_NETWORK_STACK)); + + private void setAllowedPermissions(String... permissions) { + mAllowedPermissions = new ArraySet<>(permissions); + } + + @Override + public Object getSystemService(String name) { + switch(name) { + case Context.APP_OPS_SERVICE: + return mMockAppOps; + case Context.CONNECTIVITY_SERVICE: + return mMockConnectivityMgr; + default: + return null; + } + } + + @Override + public String getSystemServiceName(Class<?> serviceClass) { + if (ConnectivityManager.class == serviceClass) { + return Context.CONNECTIVITY_SERVICE; + } + return null; + } + + @Override + public PackageManager getPackageManager() { + return mMockPkgMgr; + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + if (mAllowedPermissions.contains(permission)) { + return; + } else { + throw new SecurityException("Unavailable permission requested"); + } + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + if (mAllowedPermissions.contains(permission)) { + return PERMISSION_GRANTED; + } else { + return PERMISSION_DENIED; + } + } + } + + INetd mMockNetd; + PackageManager mMockPkgMgr; + IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig; + IpSecService mIpSecService; + Network fakeNetwork = new Network(0xAB); + int mUid = Os.getuid(); + + private static final IpSecAlgorithm AUTH_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4); + private static final IpSecAlgorithm CRYPT_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY); + private static final IpSecAlgorithm AEAD_ALGO = + new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); + private static final int REMOTE_ENCAP_PORT = 4500; + + private static final String BLESSED_PACKAGE = "blessedPackage"; + private static final String SYSTEM_PACKAGE = "systemPackage"; + private static final String BAD_PACKAGE = "badPackage"; + + public IpSecServiceParameterizedTest( + String sourceAddr, String destAddr, String localInnerAddr, int family) { + mSourceAddr = sourceAddr; + mDestinationAddr = destAddr; + mLocalInnerAddress = new LinkAddress(localInnerAddr); + mFamily = family; + } + + @Before + public void setUp() throws Exception { + mMockNetd = mock(INetd.class); + mMockPkgMgr = mock(PackageManager.class); + mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class); + mIpSecService = new IpSecService(mTestContext, mMockIpSecSrvConfig); + + // Injecting mock netd + when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd); + + // PackageManager should always return true (feature flag tests in IpSecServiceTest) + when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true); + + // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + // A system package will not be granted the app op, so this should fall back to + // a permissions check, which should pass. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE))) + .thenReturn(AppOpsManager.MODE_DEFAULT); + // A mismatch between the package name and the UID will return MODE_IGNORED. + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE))) + .thenReturn(AppOpsManager.MODE_IGNORED); + } + + //TODO: Add a test to verify SPI. + + @Test + public void testIpSecServiceReserveSpi() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + assertEquals(IpSecManager.Status.OK, spiResp.status); + assertEquals(TEST_SPI, spiResp.spi); + } + + @Test + public void testReleaseSecurityParameterIndex() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + + mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + try { + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testSecurityParameterIndexBinderDeath() throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); + + IpSecSpiResponse spiResp = + mIpSecService.allocateSecurityParameterIndex( + mDestinationAddr, TEST_SPI, new Binder()); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + + refcountedRecord.binderDied(); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + try { + userRecord.mSpiRecords.getRefcountedResourceOrThrow(spiResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt())) + .thenReturn(returnSpi); + + IpSecSpiResponse spi = + mIpSecService.allocateSecurityParameterIndex( + InetAddresses.parseNumericAddress(remoteAddress).getHostAddress(), + IpSecManager.INVALID_SECURITY_PARAMETER_INDEX, + new Binder()); + return spi.resourceId; + } + + private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception { + config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI)); + config.setSourceAddress(mSourceAddr); + config.setDestinationAddress(mDestinationAddr); + } + + private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception { + config.setEncryption(CRYPT_ALGO); + config.setAuthentication(AUTH_ALGO); + } + + private void addEncapSocketToIpSecConfig(int resourceId, IpSecConfig config) throws Exception { + config.setEncapType(IpSecTransform.ENCAP_ESPINUDP); + config.setEncapSocketResourceId(resourceId); + config.setEncapRemotePort(REMOTE_ENCAP_PORT); + } + + private void verifyTransformNetdCalledForCreatingSA( + IpSecConfig config, IpSecTransformResponse resp) throws Exception { + verifyTransformNetdCalledForCreatingSA(config, resp, 0); + } + + private void verifyTransformNetdCalledForCreatingSA( + IpSecConfig config, IpSecTransformResponse resp, int encapSocketPort) throws Exception { + IpSecAlgorithm auth = config.getAuthentication(); + IpSecAlgorithm crypt = config.getEncryption(); + IpSecAlgorithm authCrypt = config.getAuthenticatedEncryption(); + + verify(mMockNetd, times(1)) + .ipSecAddSecurityAssociation( + eq(mUid), + eq(config.getMode()), + eq(config.getSourceAddress()), + eq(config.getDestinationAddress()), + eq((config.getNetwork() != null) ? config.getNetwork().netId : 0), + eq(TEST_SPI), + eq(0), + eq(0), + eq((auth != null) ? auth.getName() : ""), + eq((auth != null) ? auth.getKey() : new byte[] {}), + eq((auth != null) ? auth.getTruncationLengthBits() : 0), + eq((crypt != null) ? crypt.getName() : ""), + eq((crypt != null) ? crypt.getKey() : new byte[] {}), + eq((crypt != null) ? crypt.getTruncationLengthBits() : 0), + eq((authCrypt != null) ? authCrypt.getName() : ""), + eq((authCrypt != null) ? authCrypt.getKey() : new byte[] {}), + eq((authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0), + eq(config.getEncapType()), + eq(encapSocketPort), + eq(config.getEncapRemotePort()), + eq(config.getXfrmInterfaceId())); + } + + @Test + public void testCreateTransform() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testCreateTransformAead() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + + ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testCreateTransportModeTransformWithEncap() throws Exception { + IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TRANSPORT); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig); + + if (mFamily == AF_INET) { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); + } else { + try { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testCreateTunnelModeTransformWithEncap() throws Exception { + IpSecUdpEncapResponse udpSock = mIpSecService.openUdpEncapsulationSocket(0, new Binder()); + + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + addEncapSocketToIpSecConfig(udpSock.resourceId, ipSecConfig); + + if (mFamily == AF_INET) { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); + } else { + try { + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); + } catch (IllegalArgumentException expected) { + } + } + } + + @Test + public void testCreateTwoTransformsWithSameSpis() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + assertEquals(IpSecManager.Status.OK, createTransformResp.status); + + // Attempting to create transform a second time with the same SPIs should throw an error... + try { + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("IpSecService should have thrown an error for reuse of SPI"); + } catch (IllegalStateException expected) { + } + + // ... even if the transform is deleted + mIpSecService.deleteTransform(createTransformResp.resourceId); + try { + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + fail("IpSecService should have thrown an error for reuse of SPI"); + } catch (IllegalStateException expected) { + } + } + + @Test + public void testReleaseOwnedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + verify(mMockNetd, times(0)) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + // quota is not released until the SPI is released by the Transform + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + } + + @Test + public void testDeleteTransform() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + mIpSecService.deleteTransform(createTransformResp.resourceId); + + verify(mMockNetd, times(1)) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); + assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); + + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + // Verify that ipSecDeleteSa was not called when the SPI was released because the + // ownedByTransform property should prevent it; (note, the called count is cumulative). + verify(mMockNetd, times(1)) + .ipSecDeleteSecurityAssociation( + anyInt(), + anyString(), + anyString(), + anyInt(), + anyInt(), + anyInt(), + anyInt()); + assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); + + try { + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testTransportModeTransformBinderDeath() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + + refcountedRecord.binderDied(); + + verify(mMockNetd) + .ipSecDeleteSecurityAssociation( + eq(mUid), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), + anyInt(), + anyInt()); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); + try { + userRecord.mTransformRecords.getRefcountedResourceOrThrow( + createTransformResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + + } + } + + @Test + public void testApplyTransportModeTransform() throws Exception { + verifyApplyTransportModeTransformCommon(false); + } + + @Test + public void testApplyTransportModeTransformReleasedSpi() throws Exception { + verifyApplyTransportModeTransformCommon(true); + } + + public void verifyApplyTransportModeTransformCommon( + boolean closeSpiBeforeApply) throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + + int resourceId = createTransformResp.resourceId; + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); + + verify(mMockNetd) + .ipSecApplyTransportModeTransform( + eq(pfd), + eq(mUid), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI)); + } + + @Test + public void testApplyTransportModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + + int resourceId = createTransformResp.resourceId; + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); + + verify(mMockNetd) + .ipSecApplyTransportModeTransform( + eq(pfd), + eq(mUid), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI)); + } + + @Test + public void testRemoveTransportModeTransform() throws Exception { + Socket socket = new Socket(); + socket.bind(null); + ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + mIpSecService.removeTransportModeTransforms(pfd); + + verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd); + } + + private IpSecTunnelInterfaceResponse createAndValidateTunnel( + String localAddr, String remoteAddr, String pkgName) throws Exception { + final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel(); + config.flags = new String[] {IF_STATE_DOWN}; + when(mMockNetd.interfaceGetCfg(anyString())).thenReturn(config); + IpSecTunnelInterfaceResponse createTunnelResp = + mIpSecService.createTunnelInterface( + mSourceAddr, mDestinationAddr, fakeNetwork, new Binder(), pkgName); + + assertNotNull(createTunnelResp); + assertEquals(IpSecManager.Status.OK, createTunnelResp.status); + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT, DIRECTION_FWD}) { + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd).ipSecAddSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(direction), + anyString(), + anyString(), + eq(0), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(createTunnelResp.resourceId)); + } + } + + return createTunnelResp; + } + + @Test + public void testCreateTunnelInterface() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + // Check that we have stored the tracking object, and retrieve it + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd) + .ipSecAddTunnelInterface( + eq(createTunnelResp.interfaceName), + eq(mSourceAddr), + eq(mDestinationAddr), + anyInt(), + anyInt(), + anyInt()); + verify(mMockNetd).interfaceSetCfg(argThat( + config -> Arrays.asList(config.flags).contains(IF_STATE_UP))); + } + + @Test + public void testDeleteTunnelInterface() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName)); + try { + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + } + } + + private Network createFakeUnderlyingNetwork(String interfaceName) { + final Network fakeNetwork = new Network(1000); + final LinkProperties fakeLp = new LinkProperties(); + fakeLp.setInterfaceName(interfaceName); + when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp); + return fakeNetwork; + } + + @Test + public void testSetNetworkForTunnelInterface() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface"); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork()); + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = new Network(1000); + + try { + mIpSecService.setNetworkForTunnelInterface( + IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE); + fail("Expected an IllegalArgumentException for invalid resource ID."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + + final Network newFakeNetwork = + createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName()); + + try { + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + fail( + "Expected an IllegalArgumentException because the underlying network is the" + + " network being exposed by this tunnel."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testTunnelInterfaceBinderDeath() throws Exception { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); + IpSecService.RefcountedResource refcountedRecord = + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + + refcountedRecord.binderDied(); + + // Verify quota and RefcountedResource objects cleaned up + assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); + verify(mMockNetd).ipSecRemoveTunnelInterface(eq(createTunnelResp.interfaceName)); + try { + userRecord.mTunnelInterfaceRecords.getRefcountedResourceOrThrow( + createTunnelResp.resourceId); + fail("Expected IllegalArgumentException on attempt to access deleted resource"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testApplyTunnelModeTransformOutbound() throws Exception { + verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformOutboundNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + verifyApplyTunnelModeTransformCommon(false /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformOutboundReleasedSpi() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_OUT); + } + + @Test + public void testApplyTunnelModeTransformInbound() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN); + } + + @Test + public void testApplyTunnelModeTransformInboundNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_IN); + } + + @Test + public void testApplyTunnelModeTransformForward() throws Exception { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD); + } + + @Test + public void testApplyTunnelModeTransformForwardNonNetworkStack() throws Exception { + mTestContext.setAllowedPermissions(android.Manifest.permission.MANAGE_IPSEC_TUNNELS); + + try { + verifyApplyTunnelModeTransformCommon(true /* closeSpiBeforeApply */, DIRECTION_FWD); + fail("Expected security exception due to use of forward policies without NETWORK_STACK" + + " or MAINLINE_NETWORK_STACK permission"); + } catch (SecurityException expected) { + } + } + + public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction) + throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + if (closeSpiBeforeApply) { + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + } + + int transformResourceId = createTransformResp.resourceId; + int tunnelResourceId = createTunnelResp.resourceId; + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE); + + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd) + .ipSecUpdateSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(direction), + anyString(), + anyString(), + eq(direction == DIRECTION_OUT ? TEST_SPI : 0), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(tunnelResourceId)); + } + + ipSecConfig.setXfrmInterfaceId(tunnelResourceId); + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + + @Test + public void testApplyTunnelModeTransformWithClosedSpi() throws Exception { + IpSecConfig ipSecConfig = new IpSecConfig(); + ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL); + addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); + addAuthAndCryptToIpSecConfig(ipSecConfig); + + IpSecTransformResponse createTransformResp = + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + + // Close SPI record + mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); + + int transformResourceId = createTransformResp.resourceId; + int tunnelResourceId = createTunnelResp.resourceId; + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); + + for (int selAddrFamily : ADDRESS_FAMILIES) { + verify(mMockNetd) + .ipSecUpdateSecurityPolicy( + eq(mUid), + eq(selAddrFamily), + eq(IpSecManager.DIRECTION_OUT), + anyString(), + anyString(), + eq(TEST_SPI), + anyInt(), // iKey/oKey + anyInt(), // mask + eq(tunnelResourceId)); + } + + ipSecConfig.setXfrmInterfaceId(tunnelResourceId); + verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); + } + + @Test + public void testAddRemoveAddressFromTunnelInterface() throws Exception { + for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName); + mIpSecService.addAddressToTunnelInterface( + createTunnelResp.resourceId, mLocalInnerAddress, pkgName); + verify(mMockNetd, times(1)) + .interfaceAddAddress( + eq(createTunnelResp.interfaceName), + eq(mLocalInnerAddress.getAddress().getHostAddress()), + eq(mLocalInnerAddress.getPrefixLength())); + mIpSecService.removeAddressFromTunnelInterface( + createTunnelResp.resourceId, mLocalInnerAddress, pkgName); + verify(mMockNetd, times(1)) + .interfaceDelAddress( + eq(createTunnelResp.interfaceName), + eq(mLocalInnerAddress.getAddress().getHostAddress()), + eq(mLocalInnerAddress.getPrefixLength())); + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, pkgName); + } + } + + @Ignore + @Test + public void testAddTunnelFailsForBadPackageName() throws Exception { + try { + IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE); + fail("Expected a SecurityException for badPackage."); + } catch (SecurityException expected) { + } + } + + @Test + public void testFeatureFlagVerification() throws Exception { + when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS))) + .thenReturn(false); + + try { + String addr = Inet4Address.getLoopbackAddress().getHostAddress(); + mIpSecService.createTunnelInterface( + addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE); + fail("Expected UnsupportedOperationException for disabled feature"); + } catch (UnsupportedOperationException expected) { + } + } +} |
