Add always-on VPN support for platform VPNs

This commit allows Platform VPNs to be started as part of always-on
mode.

Test: FrameworksNetTests passing, new tests added in subsequent CL
Test: Manually tested.
Change-Id: I5eda88e5b406a0e425eb7424665cf702e0979324
Merged-In: I5eda88e5b406a0e425eb7424665cf702e0979324
This commit is contained in:
Benedict Wong
2020-01-17 19:33:55 -08:00
parent f8af692214
commit 522f3c932d
5 changed files with 182 additions and 77 deletions

View File

@@ -126,7 +126,11 @@ public class VpnManager {
return getIntentForConfirmation();
}
/** Delete the VPN profile configuration that was provisioned by the calling app */
/**
* Delete the VPN profile configuration that was provisioned by the calling app
*
* @throws SecurityException if this would violate user settings
*/
public void deleteProvisionedVpnProfile() {
try {
mService.deleteVpnProfile(mContext.getOpPackageName());

View File

@@ -4783,7 +4783,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return false;
}
return vpn.startAlwaysOnVpn();
return vpn.startAlwaysOnVpn(mKeyStore);
}
}
@@ -4798,7 +4798,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
return vpn.isAlwaysOnPackageSupported(packageName);
return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
}
}
@@ -4819,11 +4819,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
return false;
}
if (!startAlwaysOnVpn(userId)) {
vpn.setAlwaysOnPackage(null, false, null);
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
return false;
}
}
@@ -5009,7 +5009,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
loge("Starting user already has a VPN");
return;
}
userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
mVpns.put(userId, userVpn);
if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
@@ -5080,7 +5080,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
+ userId);
vpn.startAlwaysOnVpn();
vpn.startAlwaysOnVpn(mKeyStore);
}
}
}
@@ -5102,7 +5102,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
+ userId);
vpn.setAlwaysOnPackage(null, false, null);
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
}
}
}

View File

@@ -216,14 +216,14 @@ public class Vpn {
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
* only applies to {@link VpnService} connections.
*/
private boolean mAlwaysOn = false;
@VisibleForTesting protected boolean mAlwaysOn = false;
/**
* Whether to disable traffic outside of this VPN even when the VPN is not connected. System
* apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
* not set.
* not set. Applies to all types of VPNs.
*/
private boolean mLockdown = false;
@VisibleForTesting protected boolean mLockdown = false;
/**
* Set of packages in addition to the VPN app itself that can access the network directly when
@@ -252,14 +252,14 @@ public class Vpn {
private final int mUserHandle;
public Vpn(Looper looper, Context context, INetworkManagementService netService,
@UserIdInt int userHandle) {
this(looper, context, netService, userHandle,
@UserIdInt int userHandle, @NonNull KeyStore keyStore) {
this(looper, context, netService, userHandle, keyStore,
new SystemServices(context), new Ikev2SessionCreator());
}
@VisibleForTesting
protected Vpn(Looper looper, Context context, INetworkManagementService netService,
int userHandle, SystemServices systemServices,
int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
Ikev2SessionCreator ikev2SessionCreator) {
mContext = context;
mNetd = netService;
@@ -285,7 +285,7 @@ public class Vpn {
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
updateCapabilities(null /* defaultNetwork */);
loadAlwaysOnPackage();
loadAlwaysOnPackage(keyStore);
}
/**
@@ -437,23 +437,36 @@ public class Vpn {
/**
* Checks if a VPN app supports always-on mode.
*
* In order to support the always-on feature, an app has to
* <p>In order to support the always-on feature, an app has to either have an installed
* PlatformVpnProfile, or:
*
* <ul>
* <li>target {@link VERSION_CODES#N API 24} or above, and
* <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
* meta-data field.
* <li>target {@link VERSION_CODES#N API 24} or above, and
* <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
* meta-data field.
* </ul>
*
* @param packageName the canonical package name of the VPN app
* @param keyStore the keystore instance to use for checking if the app has a Platform VPN
* profile installed.
* @return {@code true} if and only if the VPN app exists and supports always-on mode
*/
public boolean isAlwaysOnPackageSupported(String packageName) {
public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
enforceSettingsPermission();
if (packageName == null) {
return false;
}
final long oldId = Binder.clearCallingIdentity();
try {
if (getVpnProfilePrivileged(packageName, keyStore) != null) {
return true;
}
} finally {
Binder.restoreCallingIdentity(oldId);
}
PackageManager pm = mContext.getPackageManager();
ApplicationInfo appInfo = null;
try {
@@ -485,27 +498,31 @@ public class Vpn {
}
/**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
* Configures an always-on VPN connection through a specific application. This connection is
* automatically granted and persisted after a reboot.
*
* <p>The designated package should exist and declare a {@link VpnService} in its
* manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
* otherwise the call will fail.
* <p>The designated package should either have a PlatformVpnProfile installed, or declare a
* {@link VpnService} in its manifest guarded by {@link
* android.Manifest.permission.BIND_VPN_SERVICE}, otherwise the call will fail.
*
* <p>Note that this method does not check if the VPN app supports always-on mode. The check is
* delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
* method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
* delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this method
* in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
*
* @param packageName the package to designate as always-on VPN supplier.
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
* @param lockdownWhitelist packages to be whitelisted from lockdown.
* @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
public synchronized boolean setAlwaysOnPackage(
String packageName, boolean lockdown, List<String> lockdownWhitelist) {
@Nullable String packageName,
boolean lockdown,
@Nullable List<String> lockdownWhitelist,
@NonNull KeyStore keyStore) {
enforceControlPermissionOrInternalCaller();
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
saveAlwaysOnPackage();
return true;
}
@@ -513,20 +530,22 @@ public class Vpn {
}
/**
* Configures an always-on VPN connection through a specific application, the same as
* {@link #setAlwaysOnPackage}.
* Configures an always-on VPN connection through a specific application, the same as {@link
* #setAlwaysOnPackage}.
*
* Does not perform permission checks. Does not persist any of the changes to storage.
* <p>Does not perform permission checks. Does not persist any of the changes to storage.
*
* @param packageName the package to designate as always-on VPN supplier.
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
* @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
* {@code lockdown} is {@code true}. Packages must not contain commas.
* {@code lockdown} is {@code true}. Packages must not contain commas.
* @param keyStore the system keystore instance to check for profiles
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
*/
@GuardedBy("this")
private boolean setAlwaysOnPackageInternal(
String packageName, boolean lockdown, List<String> lockdownWhitelist) {
@Nullable String packageName, boolean lockdown,
@Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
return false;
@@ -542,11 +561,18 @@ public class Vpn {
}
if (packageName != null) {
// TODO: Give the minimum permission possible; if there is a Platform VPN profile, only
// grant ACTIVATE_PLATFORM_VPN.
// Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing
// both VpnService and Platform VPNs.
if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) {
final VpnProfile profile;
final long oldId = Binder.clearCallingIdentity();
try {
profile = getVpnProfilePrivileged(packageName, keyStore);
} finally {
Binder.restoreCallingIdentity(oldId);
}
// Pre-authorize new always-on VPN package.
final int grantType =
(profile == null) ? VpnManager.TYPE_VPN_SERVICE : VpnManager.TYPE_VPN_PLATFORM;
if (!setPackageAuthorization(packageName, grantType)) {
return false;
}
mAlwaysOn = true;
@@ -611,11 +637,9 @@ public class Vpn {
}
}
/**
* Load the always-on package and lockdown config from Settings.Secure
*/
/** Load the always-on package and lockdown config from Settings. */
@GuardedBy("this")
private void loadAlwaysOnPackage() {
private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
final long token = Binder.clearCallingIdentity();
try {
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -626,17 +650,21 @@ public class Vpn {
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
setAlwaysOnPackageInternal(
alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Starts the currently selected always-on VPN
*
* @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
* @return {@code true} if the service was started, the service was already connected, or there
* was no always-on VPN to start. {@code false} otherwise.
* was no always-on VPN to start. {@code false} otherwise.
*/
public boolean startAlwaysOnVpn() {
public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
final String alwaysOnPackage;
synchronized (this) {
alwaysOnPackage = getAlwaysOnPackage();
@@ -645,8 +673,8 @@ public class Vpn {
return true;
}
// Remove always-on VPN if it's not supported.
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
setAlwaysOnPackage(null, false, null);
if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
setAlwaysOnPackage(null, false, null, keyStore);
return false;
}
// Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -657,10 +685,24 @@ public class Vpn {
}
}
// Tell the OS that background services in this app need to be allowed for
// a short time, so we can bootstrap the VPN service.
final long oldId = Binder.clearCallingIdentity();
try {
// Prefer VPN profiles, if any exist.
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
if (profile != null) {
startVpnProfilePrivileged(profile, alwaysOnPackage);
// If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
// correctly parsed, and the VPN has started running in a different thread. The only
// other possibility is that the above call threw an exception, which will be
// caught below, and returns false (clearing the always-on VPN). Once started, the
// Platform VPN cannot permanantly fail, and is resiliant to temporary failures. It
// will continue retrying until shut down by the user, or always-on is toggled off.
return true;
}
// Tell the OS that background services in this app need to be allowed for
// a short time, so we can bootstrap the VPN service.
DeviceIdleController.LocalService idleController =
LocalServices.getService(DeviceIdleController.LocalService.class);
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
@@ -675,6 +717,9 @@ public class Vpn {
Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
}
} catch (Exception e) {
Log.e(TAG, "Error starting always-on VPN", e);
return false;
} finally {
Binder.restoreCallingIdentity(oldId);
}
@@ -2816,6 +2861,10 @@ public class Vpn {
return isVpnProfilePreConsented(mContext, packageName);
}
private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) {
return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner;
}
/**
* Deletes an app-provisioned VPN profile.
*
@@ -2832,6 +2881,17 @@ public class Vpn {
Binder.withCleanCallingIdentity(
() -> {
// If this profile is providing the current VPN, turn it off, disabling
// always-on as well if enabled.
if (isCurrentIkev2VpnLocked(packageName)) {
if (mAlwaysOn) {
// Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
setAlwaysOnPackage(null, false, null, keyStore);
} else {
prepareInternal(VpnConfig.LEGACY_VPN);
}
}
keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
});
}
@@ -2942,11 +3002,9 @@ public class Vpn {
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
return;
if (isCurrentIkev2VpnLocked(packageName)) {
prepareInternal(VpnConfig.LEGACY_VPN);
}
prepareInternal(VpnConfig.LEGACY_VPN);
}
/**

View File

@@ -204,6 +204,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.security.KeyStore;
import android.system.Os;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
@@ -1019,7 +1020,7 @@ public class ConnectivityServiceTest {
public MockVpn(int userId) {
super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
userId);
userId, mock(KeyStore.class));
}
public void setNetworkAgent(TestNetworkAgentWrapper agent) {

View File

@@ -72,6 +72,7 @@ import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.ArrayMap;
@@ -260,17 +261,17 @@ public class VpnTest {
assertFalse(vpn.getLockdown());
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
assertTrue(vpn.getAlwaysOn());
assertTrue(vpn.getLockdown());
// Remove always-on configuration.
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
}
@@ -284,11 +285,11 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,7 +298,7 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[1]);
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -316,7 +317,8 @@ public class VpnTest {
final UidRange user = UidRange.createForUser(primaryUser.id);
// Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
@@ -325,7 +327,8 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
// Change whitelisted app to PKGS[3].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
}));
@@ -337,7 +340,8 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
// Change the VPN app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -350,7 +354,7 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
// Remove the whitelist.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -363,7 +367,8 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[0]);
// Add the whitelist.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
}));
@@ -375,12 +380,13 @@ public class VpnTest {
assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
// Try whitelisting a package with a comma, should be rejected.
assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
assertFalse(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
// Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
// Whitelisted package should change from PGKS[1] to PKGS[2].
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -405,7 +411,7 @@ public class VpnTest {
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -499,22 +505,22 @@ public class VpnTest {
.thenReturn(Collections.singletonList(resInfo));
// null package name should return false
assertFalse(vpn.isAlwaysOnPackageSupported(null));
assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
// Pre-N apps are not supported
appInfo.targetSdkVersion = VERSION_CODES.M;
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// N+ apps are supported by default
appInfo.targetSdkVersion = VERSION_CODES.N;
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
// Apps that opt out explicitly are not supported
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
Bundle metaData = new Bundle();
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
svcInfo.metaData = metaData;
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
}
@Test
@@ -531,7 +537,7 @@ public class VpnTest {
.cancelAsUser(anyString(), anyInt(), eq(userHandle));
// Start showing a notification for disconnected once always-on.
vpn.setAlwaysOnPackage(PKGS[0], false, null);
vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
order.verify(mNotificationManager)
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
@@ -545,7 +551,7 @@ public class VpnTest {
.notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
// Notification should be cleared after unsetting always-on package.
vpn.setAlwaysOnPackage(null, false, null);
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
}
@@ -920,12 +926,48 @@ public class VpnTest {
eq(AppOpsManager.MODE_IGNORED));
}
private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
verify(mAppOps).setMode(
eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
verify(mSystemServices).settingsSecurePutStringForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
verify(mSystemServices).settingsSecurePutIntForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
eq(primaryUser.id));
verify(mSystemServices).settingsSecurePutStringForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
}
@Test
public void testSetAndStartAlwaysOnVpn() throws Exception {
final Vpn vpn = createVpn(primaryUser.id);
setMockedUsers(primaryUser);
// UID checks must return a different UID; otherwise it'll be treated as already prepared.
final int uid = Process.myUid() + 1;
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
.thenReturn(uid);
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
setAndVerifyAlwaysOnPackage(vpn, uid, false);
assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
// TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
// a subsequent CL.
}
/**
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
return new Vpn(Looper.myLooper(), mContext, mNetService,
userId, mSystemServices, mIkev2SessionCreator);
userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
}
private static void assertBlocked(Vpn vpn, int... uids) {