Keystore 2.0: Make VPN Keystore 2.0 ready.
* Use public stable API to load certificates from keystore. * Also use grants to allow racoon to use keystore keys without special exceptions in keystore. * Use LegacyProfileStore instead of Keystore for storing VPN profiles. Bug: 175068876 Bug: 171305607 Test: atest android.net.cts.Ikev2VpnTest atest android.net.cts.IpSecManagerTest atest com.android.server.connectivity.VpnTest atest com.android.server.ConnectivityServiceTest Change-Id: I27975113896ea137260a9f94a34fb1c3ca173fe3
This commit is contained in:
parent
d3c8a4011c
commit
4ba2f0c690
@ -584,6 +584,7 @@ java_library {
|
||||
"android.security.apc-java",
|
||||
"android.security.authorization-java",
|
||||
"android.security.usermanager-java",
|
||||
"android.security.vpnprofilestore-java",
|
||||
"android.system.keystore2-V1-java",
|
||||
"android.system.suspend.control.internal-java",
|
||||
"cameraprotosnano",
|
||||
|
@ -24,10 +24,7 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresFeature;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.net.VpnProfile;
|
||||
@ -35,7 +32,9 @@ import com.android.internal.net.VpnProfile;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
@ -66,6 +65,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
||||
/** Prefix for when a Private Key is stored directly in the profile @hide */
|
||||
public static final String PREFIX_INLINE = "INLINE:";
|
||||
|
||||
private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore";
|
||||
private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
|
||||
private static final String EMPTY_CERT = "";
|
||||
|
||||
@ -430,32 +430,31 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance.
|
||||
*
|
||||
* <p>Redundant authentication information (not related to profile type) will be discarded.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return fromVpnProfile(profile, null);
|
||||
private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) {
|
||||
try {
|
||||
final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
|
||||
keystore.load(null);
|
||||
final Key key = keystore.getKey(alias, null);
|
||||
if (!(key instanceof PrivateKey)) {
|
||||
throw new IllegalStateException(
|
||||
"Unexpected key type returned from android keystore.");
|
||||
}
|
||||
return (PrivateKey) key;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to load key from android keystore.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Ikev2VpnProfile from the given profile.
|
||||
*
|
||||
* @param profile the source VpnProfile to build from
|
||||
* @param keyStore the Android Keystore instance to use to retrieve the private key, or null if
|
||||
* the private key is PEM-encoded into the profile.
|
||||
* @return The IKEv2/IPsec VPN profile
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public static Ikev2VpnProfile fromVpnProfile(
|
||||
@NonNull VpnProfile profile, @Nullable KeyStore keyStore)
|
||||
throws IOException, GeneralSecurityException {
|
||||
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
||||
throws GeneralSecurityException {
|
||||
final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
|
||||
builder.setProxy(profile.proxy);
|
||||
builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
|
||||
@ -479,12 +478,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
final PrivateKey key;
|
||||
if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
|
||||
Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
|
||||
|
||||
final String alias =
|
||||
profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
|
||||
key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
|
||||
keyStore, alias, Process.myUid());
|
||||
key = getPrivateKeyFromAndroidKeystore(alias);
|
||||
} else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
|
||||
key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
|
||||
} else {
|
||||
|
142
keystore/java/android/security/LegacyVpnProfileStore.java
Normal file
142
keystore/java/android/security/LegacyVpnProfileStore.java
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.security;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
import android.security.vpnprofilestore.IVpnProfileStore;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* @hide This class allows legacy VPN access to its profiles that were stored in Keystore.
|
||||
* The storage of unstructured blobs in Android Keystore is going away, because there is no
|
||||
* architectural or security benefit of storing profiles in keystore over storing them
|
||||
* in the file system. This class allows access to the blobs that still exist in keystore.
|
||||
* And it stores new blob in a database that is still owned by Android Keystore.
|
||||
*/
|
||||
public class LegacyVpnProfileStore {
|
||||
private static final String TAG = "LegacyVpnProfileStore";
|
||||
|
||||
public static final int SYSTEM_ERROR = IVpnProfileStore.ERROR_SYSTEM_ERROR;
|
||||
public static final int PROFILE_NOT_FOUND = IVpnProfileStore.ERROR_PROFILE_NOT_FOUND;
|
||||
|
||||
private static final String VPN_PROFILE_STORE_SERVICE_NAME = "android.security.vpnprofilestore";
|
||||
|
||||
private static IVpnProfileStore getService() {
|
||||
return IVpnProfileStore.Stub.asInterface(
|
||||
ServiceManager.checkService(VPN_PROFILE_STORE_SERVICE_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the profile under the alias in the profile database. Existing profiles by the
|
||||
* same name will be replaced.
|
||||
* @param alias The name of the profile
|
||||
* @param profile The profile.
|
||||
* @return true if the profile was successfully added. False otherwise.
|
||||
* @hide
|
||||
*/
|
||||
public static boolean put(@NonNull String alias, @NonNull byte[] profile) {
|
||||
try {
|
||||
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||
getService().put(alias, profile);
|
||||
return true;
|
||||
} else {
|
||||
return KeyStore.getInstance().put(
|
||||
alias, profile, KeyStore.UID_SELF, 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to put vpn profile.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a profile by the name alias from the profile database.
|
||||
* @param alias Name of the profile to retrieve.
|
||||
* @return The unstructured blob, that is the profile that was stored using
|
||||
* LegacyVpnProfileStore#put or with
|
||||
* android.security.Keystore.put(Credentials.VPN + alias).
|
||||
* Returns null if no profile was found.
|
||||
* @hide
|
||||
*/
|
||||
public static byte[] get(@NonNull String alias) {
|
||||
try {
|
||||
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||
return getService().get(alias);
|
||||
} else {
|
||||
return KeyStore.getInstance().get(alias, true /* suppressKeyNotFoundWarning */);
|
||||
}
|
||||
} catch (ServiceSpecificException e) {
|
||||
if (e.errorCode != PROFILE_NOT_FOUND) {
|
||||
Log.e(TAG, "Failed to get vpn profile.", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to get vpn profile.", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a profile by the name alias from the profile database.
|
||||
* @param alias Name of the profile to be removed.
|
||||
* @return True if a profile was removed. False if no such profile was found.
|
||||
* @hide
|
||||
*/
|
||||
public static boolean remove(@NonNull String alias) {
|
||||
try {
|
||||
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||
getService().remove(alias);
|
||||
return true;
|
||||
} else {
|
||||
return KeyStore.getInstance().delete(alias);
|
||||
}
|
||||
} catch (ServiceSpecificException e) {
|
||||
if (e.errorCode != PROFILE_NOT_FOUND) {
|
||||
Log.e(TAG, "Failed to remove vpn profile.", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to remove vpn profile.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the vpn profiles stored in the database.
|
||||
* @return An array of strings representing the aliases stored in the profile database.
|
||||
* The return value may be empty but never null.
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull String[] list(@NonNull String prefix) {
|
||||
try {
|
||||
if (AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||
final String[] aliases = getService().list(prefix);
|
||||
for (int i = 0; i < aliases.length; ++i) {
|
||||
aliases[i] = aliases[i].substring(prefix.length());
|
||||
}
|
||||
return aliases;
|
||||
} else {
|
||||
final String[] result = KeyStore.getInstance().list(prefix);
|
||||
return result != null ? result : new String[0];
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to list vpn profiles.", e);
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
}
|
@ -47,7 +47,6 @@ import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
@ -60,6 +59,7 @@ import com.android.internal.net.VpnProfile;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.connectivity.Vpn;
|
||||
import com.android.server.connectivity.VpnProfileStore;
|
||||
import com.android.server.net.LockdownVpnTracker;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@ -83,7 +83,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
private final Dependencies mDeps;
|
||||
|
||||
private final ConnectivityManager mCm;
|
||||
private final KeyStore mKeyStore;
|
||||
private final VpnProfileStore mVpnProfileStore;
|
||||
private final INetworkManagementService mNMS;
|
||||
private final INetd mNetd;
|
||||
private final UserManager mUserManager;
|
||||
@ -114,9 +114,9 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
return new HandlerThread("VpnManagerService");
|
||||
}
|
||||
|
||||
/** Returns the KeyStore instance to be used by this class. */
|
||||
public KeyStore getKeyStore() {
|
||||
return KeyStore.getInstance();
|
||||
/** Return the VpnProfileStore to be used by this class */
|
||||
public VpnProfileStore getVpnProfileStore() {
|
||||
return new VpnProfileStore();
|
||||
}
|
||||
|
||||
public INetd getNetd() {
|
||||
@ -135,7 +135,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
mHandlerThread = mDeps.makeHandlerThread();
|
||||
mHandlerThread.start();
|
||||
mHandler = mHandlerThread.getThreadHandler();
|
||||
mKeyStore = mDeps.getKeyStore();
|
||||
mVpnProfileStore = mDeps.getVpnProfileStore();
|
||||
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
|
||||
mCm = mContext.getSystemService(ConnectivityManager.class);
|
||||
mNMS = mDeps.getINetworkManagementService();
|
||||
@ -289,7 +289,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
|
||||
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||
synchronized (mVpns) {
|
||||
return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
|
||||
return mVpns.get(user).provisionVpnProfile(packageName, profile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,7 +307,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
public void deleteVpnProfile(@NonNull String packageName) {
|
||||
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||
synchronized (mVpns) {
|
||||
mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
|
||||
mVpns.get(user).deleteVpnProfile(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,7 +325,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||
synchronized (mVpns) {
|
||||
throwIfLockdownEnabled();
|
||||
mVpns.get(user).startVpnProfile(packageName, mKeyStore);
|
||||
mVpns.get(user).startVpnProfile(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,7 +358,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
}
|
||||
synchronized (mVpns) {
|
||||
throwIfLockdownEnabled();
|
||||
mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
|
||||
mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress);
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +396,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
}
|
||||
|
||||
private boolean isLockdownVpnEnabled() {
|
||||
return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
|
||||
return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -417,14 +417,14 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
|
||||
byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN);
|
||||
if (profileTag == null) {
|
||||
loge("Lockdown VPN configured but cannot be read from keystore");
|
||||
return false;
|
||||
}
|
||||
String profileName = new String(profileTag);
|
||||
final VpnProfile profile = VpnProfile.decode(
|
||||
profileName, mKeyStore.get(Credentials.VPN + profileName));
|
||||
profileName, mVpnProfileStore.get(Credentials.VPN + profileName));
|
||||
if (profile == null) {
|
||||
loge("Lockdown VPN configured invalid profile " + profileName);
|
||||
setLockdownTracker(null);
|
||||
@ -437,7 +437,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
return false;
|
||||
}
|
||||
setLockdownTracker(
|
||||
new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile));
|
||||
new LockdownVpnTracker(mContext, mHandler, vpn, profile));
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
return false;
|
||||
}
|
||||
|
||||
return vpn.startAlwaysOnVpn(mKeyStore);
|
||||
return vpn.startAlwaysOnVpn();
|
||||
}
|
||||
}
|
||||
|
||||
@ -510,7 +510,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
logw("User " + userId + " has no Vpn configuration");
|
||||
return false;
|
||||
}
|
||||
return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
|
||||
return vpn.isAlwaysOnPackageSupported(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -531,11 +531,11 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
logw("User " + userId + " has no Vpn configuration");
|
||||
return false;
|
||||
}
|
||||
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
|
||||
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) {
|
||||
return false;
|
||||
}
|
||||
if (!startAlwaysOnVpn(userId)) {
|
||||
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -705,7 +705,8 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
loge("Starting user already has a VPN");
|
||||
return;
|
||||
}
|
||||
userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore);
|
||||
userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId,
|
||||
new VpnProfileStore());
|
||||
mVpns.put(userId, userVpn);
|
||||
if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
|
||||
updateLockdownVpn();
|
||||
@ -777,7 +778,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
|
||||
log("Restarting always-on VPN package " + packageName + " for user "
|
||||
+ userId);
|
||||
vpn.startAlwaysOnVpn(mKeyStore);
|
||||
vpn.startAlwaysOnVpn();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -798,7 +799,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
|
||||
log("Removing always-on VPN package " + packageName + " for user "
|
||||
+ userId);
|
||||
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -843,7 +844,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
||||
if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mKeyStore.delete(Credentials.LOCKDOWN_VPN);
|
||||
mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
|
||||
mLockdownEnabled = false;
|
||||
setLockdownTracker(null);
|
||||
} finally {
|
||||
|
@ -101,7 +101,12 @@ import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.security.KeyStore2;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.system.keystore2.Domain;
|
||||
import android.system.keystore2.KeyDescriptor;
|
||||
import android.system.keystore2.KeyPermission;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
@ -132,6 +137,12 @@ import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -157,6 +168,7 @@ public class Vpn {
|
||||
private static final String TAG = "Vpn";
|
||||
private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
|
||||
private static final boolean LOGD = true;
|
||||
private static final String ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore";
|
||||
|
||||
// Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
|
||||
// the device idle allowlist during service launch and VPN bootstrap.
|
||||
@ -216,6 +228,13 @@ public class Vpn {
|
||||
private final Ikev2SessionCreator mIkev2SessionCreator;
|
||||
private final UserManager mUserManager;
|
||||
|
||||
private final VpnProfileStore mVpnProfileStore;
|
||||
|
||||
@VisibleForTesting
|
||||
VpnProfileStore getVpnProfileStore() {
|
||||
return mVpnProfileStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
|
||||
* only applies to {@link VpnService} connections.
|
||||
@ -393,24 +412,25 @@ public class Vpn {
|
||||
}
|
||||
|
||||
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
|
||||
@UserIdInt int userId, @NonNull KeyStore keyStore) {
|
||||
this(looper, context, new Dependencies(), netService, netd, userId, keyStore,
|
||||
@UserIdInt int userId, VpnProfileStore vpnProfileStore) {
|
||||
this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore,
|
||||
new SystemServices(context), new Ikev2SessionCreator());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Vpn(Looper looper, Context context, Dependencies deps,
|
||||
INetworkManagementService netService, INetd netd, @UserIdInt int userId,
|
||||
@NonNull KeyStore keyStore) {
|
||||
this(looper, context, deps, netService, netd, userId, keyStore,
|
||||
VpnProfileStore vpnProfileStore) {
|
||||
this(looper, context, deps, netService, netd, userId, vpnProfileStore,
|
||||
new SystemServices(context), new Ikev2SessionCreator());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected Vpn(Looper looper, Context context, Dependencies deps,
|
||||
INetworkManagementService netService, INetd netd,
|
||||
int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
|
||||
int userId, VpnProfileStore vpnProfileStore, SystemServices systemServices,
|
||||
Ikev2SessionCreator ikev2SessionCreator) {
|
||||
mVpnProfileStore = vpnProfileStore;
|
||||
mContext = context;
|
||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||
mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
|
||||
@ -446,7 +466,7 @@ public class Vpn {
|
||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
|
||||
|
||||
loadAlwaysOnPackage(keyStore);
|
||||
loadAlwaysOnPackage();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -567,11 +587,9 @@ public class Vpn {
|
||||
* </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, @NonNull KeyStore keyStore) {
|
||||
public boolean isAlwaysOnPackageSupported(String packageName) {
|
||||
enforceSettingsPermission();
|
||||
|
||||
if (packageName == null) {
|
||||
@ -580,7 +598,7 @@ public class Vpn {
|
||||
|
||||
final long oldId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (getVpnProfilePrivileged(packageName, keyStore) != null) {
|
||||
if (getVpnProfilePrivileged(packageName) != null) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
@ -632,17 +650,15 @@ public class Vpn {
|
||||
* @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 lockdownAllowlist packages to be allowed 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(
|
||||
@Nullable String packageName,
|
||||
boolean lockdown,
|
||||
@Nullable List<String> lockdownAllowlist,
|
||||
@NonNull KeyStore keyStore) {
|
||||
@Nullable List<String> lockdownAllowlist) {
|
||||
enforceControlPermissionOrInternalCaller();
|
||||
|
||||
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) {
|
||||
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
|
||||
saveAlwaysOnPackage();
|
||||
return true;
|
||||
}
|
||||
@ -659,13 +675,12 @@ public class Vpn {
|
||||
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
||||
* @param lockdownAllowlist packages to be allowed to bypass lockdown. This is only used if
|
||||
* {@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(
|
||||
@Nullable String packageName, boolean lockdown,
|
||||
@Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) {
|
||||
@Nullable List<String> lockdownAllowlist) {
|
||||
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
|
||||
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
|
||||
return false;
|
||||
@ -684,7 +699,7 @@ public class Vpn {
|
||||
final VpnProfile profile;
|
||||
final long oldId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
profile = getVpnProfilePrivileged(packageName, keyStore);
|
||||
profile = getVpnProfilePrivileged(packageName);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(oldId);
|
||||
}
|
||||
@ -759,7 +774,7 @@ public class Vpn {
|
||||
|
||||
/** Load the always-on package and lockdown config from Settings. */
|
||||
@GuardedBy("this")
|
||||
private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
|
||||
private void loadAlwaysOnPackage() {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
|
||||
@ -771,7 +786,7 @@ public class Vpn {
|
||||
final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
|
||||
? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
|
||||
setAlwaysOnPackageInternal(
|
||||
alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore);
|
||||
alwaysOnPackage, alwaysOnLockdown, allowedPackages);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
@ -780,11 +795,10 @@ public class Vpn {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
|
||||
public boolean startAlwaysOnVpn() {
|
||||
final String alwaysOnPackage;
|
||||
synchronized (this) {
|
||||
alwaysOnPackage = getAlwaysOnPackage();
|
||||
@ -793,8 +807,8 @@ public class Vpn {
|
||||
return true;
|
||||
}
|
||||
// Remove always-on VPN if it's not supported.
|
||||
if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
|
||||
setAlwaysOnPackage(null, false, null, keyStore);
|
||||
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
|
||||
setAlwaysOnPackage(null, false, null);
|
||||
return false;
|
||||
}
|
||||
// Skip if the service is already established. This isn't bulletproof: it's not bound
|
||||
@ -808,10 +822,9 @@ public class Vpn {
|
||||
final long oldId = Binder.clearCallingIdentity();
|
||||
try {
|
||||
// Prefer VPN profiles, if any exist.
|
||||
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
|
||||
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage);
|
||||
if (profile != null) {
|
||||
startVpnProfilePrivileged(profile, alwaysOnPackage,
|
||||
null /* keyStore for private key retrieval - unneeded */);
|
||||
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
|
||||
@ -2013,27 +2026,83 @@ public class Vpn {
|
||||
* secondary thread to perform connection work, returning quickly.
|
||||
*
|
||||
* Should only be called to respond to Binder requests as this enforces caller permission. Use
|
||||
* {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the
|
||||
* {@link #startLegacyVpnPrivileged(VpnProfile, Network, LinkProperties)} to skip the
|
||||
* permission check only when the caller is trusted (or the call is initiated by the system).
|
||||
*/
|
||||
public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying,
|
||||
public void startLegacyVpn(VpnProfile profile, @Nullable Network underlying,
|
||||
LinkProperties egress) {
|
||||
enforceControlPermission();
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
startLegacyVpnPrivileged(profile, keyStore, underlying, egress);
|
||||
startLegacyVpnPrivileged(profile, underlying, egress);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
private String makeKeystoreEngineGrantString(String alias) {
|
||||
if (alias == null) {
|
||||
return null;
|
||||
}
|
||||
// If Keystore 2.0 is not enabled the legacy private key prefix is used.
|
||||
if (!AndroidKeyStoreProvider.isKeystore2Enabled()) {
|
||||
return Credentials.USER_PRIVATE_KEY + alias;
|
||||
}
|
||||
final KeyStore2 keystore2 = KeyStore2.getInstance();
|
||||
|
||||
KeyDescriptor key = new KeyDescriptor();
|
||||
key.domain = Domain.APP;
|
||||
key.nspace = KeyProperties.NAMESPACE_APPLICATION;
|
||||
key.alias = alias;
|
||||
key.blob = null;
|
||||
|
||||
final int grantAccessVector = KeyPermission.USE | KeyPermission.GET_INFO;
|
||||
|
||||
try {
|
||||
// The native vpn daemon is running as VPN_UID. This tells Keystore 2.0
|
||||
// to allow a process running with this UID to access the key designated by
|
||||
// the KeyDescriptor `key`. `grant` returns a new KeyDescriptor with a grant
|
||||
// identifier. This identifier needs to be communicated to the vpn daemon.
|
||||
key = keystore2.grant(key, android.os.Process.VPN_UID, grantAccessVector);
|
||||
} catch (android.security.KeyStoreException e) {
|
||||
Log.e(TAG, "Failed to get grant for keystore key.", e);
|
||||
throw new IllegalStateException("Failed to get grant for keystore key.", e);
|
||||
}
|
||||
|
||||
// Turn the grant identifier into a string as understood by the keystore boringssl engine
|
||||
// in system/security/keystore-engine.
|
||||
return KeyStore2.makeKeystoreEngineGrantString(key.nspace);
|
||||
}
|
||||
|
||||
private String getCaCertificateFromKeystoreAsPem(@NonNull KeyStore keystore,
|
||||
@NonNull String alias)
|
||||
throws KeyStoreException, IOException, CertificateEncodingException {
|
||||
if (keystore.isCertificateEntry(alias)) {
|
||||
final Certificate cert = keystore.getCertificate(alias);
|
||||
if (cert == null) return null;
|
||||
return new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
final Certificate[] certs = keystore.getCertificateChain(alias);
|
||||
// If there is none or one entry it means there is no CA entry associated with this
|
||||
// alias.
|
||||
if (certs == null || certs.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
// If this is not a (pure) certificate entry, then there is a user certificate which
|
||||
// will be included at the beginning of the certificate chain. But the caller of this
|
||||
// function does not expect this certificate to be included, so we cut it off.
|
||||
return new String(Credentials.convertToPem(
|
||||
Arrays.copyOfRange(certs, 1, certs.length)), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not
|
||||
* Like {@link #startLegacyVpn(VpnProfile, Network, LinkProperties)}, but does not
|
||||
* check permissions under the assumption that the caller is the system.
|
||||
*
|
||||
* Callers are responsible for checking permissions if needed.
|
||||
*/
|
||||
public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
|
||||
public void startLegacyVpnPrivileged(VpnProfile profile,
|
||||
@Nullable Network underlying, @NonNull LinkProperties egress) {
|
||||
UserInfo user = mUserManager.getUserInfo(mUserId);
|
||||
if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
|
||||
@ -2050,18 +2119,27 @@ public class Vpn {
|
||||
String userCert = "";
|
||||
String caCert = "";
|
||||
String serverCert = "";
|
||||
if (!profile.ipsecUserCert.isEmpty()) {
|
||||
privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
|
||||
byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
|
||||
userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (!profile.ipsecCaCert.isEmpty()) {
|
||||
byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
|
||||
caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
||||
}
|
||||
if (!profile.ipsecServerCert.isEmpty()) {
|
||||
byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
|
||||
serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
||||
|
||||
try {
|
||||
final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
|
||||
keystore.load(null);
|
||||
if (!profile.ipsecUserCert.isEmpty()) {
|
||||
privateKey = profile.ipsecUserCert;
|
||||
final Certificate cert = keystore.getCertificate(profile.ipsecUserCert);
|
||||
userCert = (cert == null) ? null
|
||||
: new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
|
||||
}
|
||||
if (!profile.ipsecCaCert.isEmpty()) {
|
||||
caCert = getCaCertificateFromKeystoreAsPem(keystore, profile.ipsecCaCert);
|
||||
}
|
||||
if (!profile.ipsecServerCert.isEmpty()) {
|
||||
final Certificate cert = keystore.getCertificate(profile.ipsecServerCert);
|
||||
serverCert = (cert == null) ? null
|
||||
: new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (CertificateException | KeyStoreException | IOException
|
||||
| NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Failed to load credentials from AndroidKeyStore", e);
|
||||
}
|
||||
if (userCert == null || caCert == null || serverCert == null) {
|
||||
throw new IllegalStateException("Cannot load credentials");
|
||||
@ -2082,7 +2160,7 @@ public class Vpn {
|
||||
|
||||
// Start VPN profile
|
||||
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
|
||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
|
||||
return;
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
// Ikev2VpnProfiles expect a base64-encoded preshared key.
|
||||
@ -2091,7 +2169,7 @@ public class Vpn {
|
||||
|
||||
// Start VPN profile
|
||||
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
|
||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
|
||||
return;
|
||||
case VpnProfile.TYPE_L2TP_IPSEC_PSK:
|
||||
racoon = new String[] {
|
||||
@ -2101,8 +2179,8 @@ public class Vpn {
|
||||
break;
|
||||
case VpnProfile.TYPE_L2TP_IPSEC_RSA:
|
||||
racoon = new String[] {
|
||||
iface, profile.server, "udprsa", privateKey, userCert,
|
||||
caCert, serverCert, "1701",
|
||||
iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey),
|
||||
userCert, caCert, serverCert, "1701",
|
||||
};
|
||||
break;
|
||||
case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
|
||||
@ -2113,8 +2191,8 @@ public class Vpn {
|
||||
break;
|
||||
case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
|
||||
racoon = new String[] {
|
||||
iface, profile.server, "xauthrsa", privateKey, userCert,
|
||||
caCert, serverCert, profile.username, profile.password, "", gateway,
|
||||
iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey),
|
||||
userCert, caCert, serverCert, profile.username, profile.password, "", gateway,
|
||||
};
|
||||
break;
|
||||
case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
|
||||
@ -3049,14 +3127,12 @@ public class Vpn {
|
||||
*
|
||||
* @param packageName the package name of the app provisioning this profile
|
||||
* @param profile the profile to be stored and provisioned
|
||||
* @param keyStore the System keystore instance to save VPN profiles
|
||||
* @returns whether or not the app has already been granted user consent
|
||||
*/
|
||||
public synchronized boolean provisionVpnProfile(
|
||||
@NonNull String packageName, @NonNull VpnProfile profile, @NonNull KeyStore keyStore) {
|
||||
@NonNull String packageName, @NonNull VpnProfile profile) {
|
||||
checkNotNull(packageName, "No package name provided");
|
||||
checkNotNull(profile, "No profile provided");
|
||||
checkNotNull(keyStore, "KeyStore missing");
|
||||
|
||||
verifyCallingUidAndPackage(packageName);
|
||||
enforceNotRestrictedUser();
|
||||
@ -3075,11 +3151,9 @@ public class Vpn {
|
||||
// Permissions checked during startVpnProfile()
|
||||
Binder.withCleanCallingIdentity(
|
||||
() -> {
|
||||
keyStore.put(
|
||||
getVpnProfileStore().put(
|
||||
getProfileNameForPackage(packageName),
|
||||
encodedProfile,
|
||||
Process.SYSTEM_UID,
|
||||
0 /* flags */);
|
||||
encodedProfile);
|
||||
});
|
||||
|
||||
// TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
|
||||
@ -3097,12 +3171,10 @@ public class Vpn {
|
||||
* Deletes an app-provisioned VPN profile.
|
||||
*
|
||||
* @param packageName the package name of the app provisioning this profile
|
||||
* @param keyStore the System keystore instance to save VPN profiles
|
||||
*/
|
||||
public synchronized void deleteVpnProfile(
|
||||
@NonNull String packageName, @NonNull KeyStore keyStore) {
|
||||
@NonNull String packageName) {
|
||||
checkNotNull(packageName, "No package name provided");
|
||||
checkNotNull(keyStore, "KeyStore missing");
|
||||
|
||||
verifyCallingUidAndPackage(packageName);
|
||||
enforceNotRestrictedUser();
|
||||
@ -3114,13 +3186,13 @@ public class Vpn {
|
||||
if (isCurrentIkev2VpnLocked(packageName)) {
|
||||
if (mAlwaysOn) {
|
||||
// Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
|
||||
setAlwaysOnPackage(null, false, null, keyStore);
|
||||
setAlwaysOnPackage(null, false, null);
|
||||
} else {
|
||||
prepareInternal(VpnConfig.LEGACY_VPN);
|
||||
}
|
||||
}
|
||||
|
||||
keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
|
||||
getVpnProfileStore().remove(getProfileNameForPackage(packageName));
|
||||
});
|
||||
}
|
||||
|
||||
@ -3132,13 +3204,13 @@ public class Vpn {
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) {
|
||||
VpnProfile getVpnProfilePrivileged(@NonNull String packageName) {
|
||||
if (!mDeps.isCallerSystem()) {
|
||||
Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID ");
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName));
|
||||
final byte[] encoded = getVpnProfileStore().get(getProfileNameForPackage(packageName));
|
||||
if (encoded == null) return null;
|
||||
|
||||
return VpnProfile.decode("" /* Key unused */, encoded);
|
||||
@ -3152,12 +3224,10 @@ public class Vpn {
|
||||
* will not match during appop checks.
|
||||
*
|
||||
* @param packageName the package name of the app provisioning this profile
|
||||
* @param keyStore the System keystore instance to retrieve VPN profiles
|
||||
*/
|
||||
public synchronized void startVpnProfile(
|
||||
@NonNull String packageName, @NonNull KeyStore keyStore) {
|
||||
@NonNull String packageName) {
|
||||
checkNotNull(packageName, "No package name provided");
|
||||
checkNotNull(keyStore, "KeyStore missing");
|
||||
|
||||
enforceNotRestrictedUser();
|
||||
|
||||
@ -3168,18 +3238,17 @@ public class Vpn {
|
||||
|
||||
Binder.withCleanCallingIdentity(
|
||||
() -> {
|
||||
final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore);
|
||||
final VpnProfile profile = getVpnProfilePrivileged(packageName);
|
||||
if (profile == null) {
|
||||
throw new IllegalArgumentException("No profile found for " + packageName);
|
||||
}
|
||||
|
||||
startVpnProfilePrivileged(profile, packageName,
|
||||
null /* keyStore for private key retrieval - unneeded */);
|
||||
startVpnProfilePrivileged(profile, packageName);
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void startVpnProfilePrivileged(
|
||||
@NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) {
|
||||
@NonNull VpnProfile profile, @NonNull String packageName) {
|
||||
// Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(),
|
||||
// by the Setting app via startLegacyVpn(), or by ConnectivityService via
|
||||
// startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the
|
||||
@ -3210,7 +3279,7 @@ public class Vpn {
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||
mVpnRunner =
|
||||
new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
|
||||
new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
|
||||
mVpnRunner.start();
|
||||
break;
|
||||
default:
|
||||
@ -3218,7 +3287,7 @@ public class Vpn {
|
||||
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
|
||||
break;
|
||||
}
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
} catch (GeneralSecurityException e) {
|
||||
// Reset mConfig
|
||||
mConfig = null;
|
||||
|
||||
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.connectivity;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.security.LegacyVpnProfileStore;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Mockable indirection to the actual profile store.
|
||||
* @hide
|
||||
*/
|
||||
public class VpnProfileStore {
|
||||
/**
|
||||
* Stores the profile under the alias in the profile database. Existing profiles by the
|
||||
* same name will be replaced.
|
||||
* @param alias The name of the profile
|
||||
* @param profile The profile.
|
||||
* @return true if the profile was successfully added. False otherwise.
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean put(@NonNull String alias, @NonNull byte[] profile) {
|
||||
return LegacyVpnProfileStore.put(alias, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a profile by the name alias from the profile database.
|
||||
* @param alias Name of the profile to retrieve.
|
||||
* @return The unstructured blob, that is the profile that was stored using
|
||||
* LegacyVpnProfileStore#put or with
|
||||
* android.security.Keystore.put(Credentials.VPN + alias).
|
||||
* Returns null if no profile was found.
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public byte[] get(@NonNull String alias) {
|
||||
return LegacyVpnProfileStore.get(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a profile by the name alias from the profile database.
|
||||
* @param alias Name of the profile to be removed.
|
||||
* @return True if a profile was removed. False if no such profile was found.
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean remove(@NonNull String alias) {
|
||||
return LegacyVpnProfileStore.remove(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the vpn profiles stored in the database.
|
||||
* @return An array of strings representing the aliases stored in the profile database.
|
||||
* The return value may be empty but never null.
|
||||
* @hide
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public @NonNull String[] list(@NonNull String prefix) {
|
||||
return LegacyVpnProfileStore.list(prefix);
|
||||
}
|
||||
}
|
@ -35,7 +35,6 @@ import android.net.Network;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Handler;
|
||||
import android.security.KeyStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@ -63,7 +62,6 @@ public class LockdownVpnTracker {
|
||||
@NonNull private final Handler mHandler;
|
||||
@NonNull private final Vpn mVpn;
|
||||
@NonNull private final VpnProfile mProfile;
|
||||
@NonNull private final KeyStore mKeyStore;
|
||||
|
||||
@NonNull private final Object mStateLock = new Object();
|
||||
|
||||
@ -132,7 +130,6 @@ public class LockdownVpnTracker {
|
||||
|
||||
public LockdownVpnTracker(@NonNull Context context,
|
||||
@NonNull Handler handler,
|
||||
@NonNull KeyStore keyStore,
|
||||
@NonNull Vpn vpn,
|
||||
@NonNull VpnProfile profile) {
|
||||
mContext = Objects.requireNonNull(context);
|
||||
@ -140,7 +137,6 @@ public class LockdownVpnTracker {
|
||||
mHandler = Objects.requireNonNull(handler);
|
||||
mVpn = Objects.requireNonNull(vpn);
|
||||
mProfile = Objects.requireNonNull(profile);
|
||||
mKeyStore = Objects.requireNonNull(keyStore);
|
||||
mNotificationManager = mContext.getSystemService(NotificationManager.class);
|
||||
|
||||
final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
|
||||
@ -212,7 +208,7 @@ public class LockdownVpnTracker {
|
||||
// network is the system default. So, if the VPN is up and underlying network
|
||||
// (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
|
||||
// changed to match the new default network (e.g., cell).
|
||||
mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
|
||||
mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp);
|
||||
} catch (IllegalStateException e) {
|
||||
mAcceptedEgressIface = null;
|
||||
Log.e(TAG, "Failed to start VPN", e);
|
||||
|
@ -244,7 +244,6 @@ import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.system.Os;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||
@ -276,6 +275,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy
|
||||
import com.android.server.connectivity.ProxyTracker;
|
||||
import com.android.server.connectivity.QosCallbackTracker;
|
||||
import com.android.server.connectivity.Vpn;
|
||||
import com.android.server.connectivity.VpnProfileStore;
|
||||
import com.android.server.net.NetworkPinner;
|
||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||
import com.android.testutils.ExceptionUtils;
|
||||
@ -431,7 +431,7 @@ public class ConnectivityServiceTest {
|
||||
@Mock MockableSystemProperties mSystemProperties;
|
||||
@Mock EthernetManager mEthernetManager;
|
||||
@Mock NetworkPolicyManager mNetworkPolicyManager;
|
||||
@Mock KeyStore mKeyStore;
|
||||
@Mock VpnProfileStore mVpnProfileStore;
|
||||
@Mock SystemConfigManager mSystemConfigManager;
|
||||
|
||||
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
|
||||
@ -1115,7 +1115,7 @@ public class ConnectivityServiceTest {
|
||||
return mDeviceIdleInternal;
|
||||
}
|
||||
},
|
||||
mNetworkManagementService, mMockNetd, userId, mKeyStore);
|
||||
mNetworkManagementService, mMockNetd, userId, mVpnProfileStore);
|
||||
}
|
||||
|
||||
public void setUids(Set<UidRange> uids) {
|
||||
@ -1294,8 +1294,9 @@ public class ConnectivityServiceTest {
|
||||
return mVMSHandlerThread;
|
||||
}
|
||||
|
||||
public KeyStore getKeyStore() {
|
||||
return mKeyStore;
|
||||
@Override
|
||||
public VpnProfileStore getVpnProfileStore() {
|
||||
return mVpnProfileStore;
|
||||
}
|
||||
|
||||
public INetd getNetd() {
|
||||
@ -7487,8 +7488,7 @@ public class ConnectivityServiceTest {
|
||||
private void setupLegacyLockdownVpn() {
|
||||
final String profileName = "testVpnProfile";
|
||||
final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
|
||||
when(mKeyStore.contains(Credentials.LOCKDOWN_VPN)).thenReturn(true);
|
||||
when(mKeyStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
|
||||
when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
|
||||
|
||||
final VpnProfile profile = new VpnProfile(profileName);
|
||||
profile.name = "My VPN";
|
||||
@ -7496,7 +7496,7 @@ public class ConnectivityServiceTest {
|
||||
profile.dnsServers = "8.8.8.8";
|
||||
profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
|
||||
final byte[] encodedProfile = profile.encode();
|
||||
when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
|
||||
when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
|
||||
}
|
||||
|
||||
private void establishLegacyLockdownVpn(Network underlying) throws Exception {
|
||||
|
@ -91,7 +91,6 @@ import android.os.UserManager;
|
||||
import android.os.test.TestLooper;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.security.KeyStore;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Range;
|
||||
@ -196,7 +195,7 @@ public class VpnTest {
|
||||
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
|
||||
@Mock private ConnectivityManager mConnectivityManager;
|
||||
@Mock private IpSecService mIpSecService;
|
||||
@Mock private KeyStore mKeyStore;
|
||||
@Mock private VpnProfileStore mVpnProfileStore;
|
||||
private final VpnProfile mVpnProfile;
|
||||
|
||||
private IpSecManager mIpSecManager;
|
||||
@ -333,17 +332,17 @@ public class VpnTest {
|
||||
assertFalse(vpn.getLockdown());
|
||||
|
||||
// Set always-on without lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
|
||||
assertTrue(vpn.getAlwaysOn());
|
||||
assertFalse(vpn.getLockdown());
|
||||
|
||||
// Set always-on with lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
|
||||
assertTrue(vpn.getAlwaysOn());
|
||||
assertTrue(vpn.getLockdown());
|
||||
|
||||
// Remove always-on configuration.
|
||||
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
|
||||
assertFalse(vpn.getAlwaysOn());
|
||||
assertFalse(vpn.getLockdown());
|
||||
}
|
||||
@ -354,17 +353,17 @@ public class VpnTest {
|
||||
final UidRange user = PRI_USER_RANGE;
|
||||
|
||||
// Set always-on without lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
|
||||
|
||||
// Set always-on with lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
}));
|
||||
|
||||
// Switch to another app.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
@ -382,14 +381,14 @@ public class VpnTest {
|
||||
|
||||
// Set always-on with lockdown and allow app PKGS[2] from lockdown.
|
||||
assertTrue(vpn.setAlwaysOnPackage(
|
||||
PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
|
||||
PKGS[1], true, Collections.singletonList(PKGS[2])));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||
}));
|
||||
// Change allowed app list to PKGS[3].
|
||||
assertTrue(vpn.setAlwaysOnPackage(
|
||||
PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
|
||||
PKGS[1], true, Collections.singletonList(PKGS[3])));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||
}));
|
||||
@ -400,7 +399,7 @@ public class VpnTest {
|
||||
|
||||
// Change the VPN app.
|
||||
assertTrue(vpn.setAlwaysOnPackage(
|
||||
PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
|
||||
PKGS[0], true, Collections.singletonList(PKGS[3])));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
|
||||
@ -411,7 +410,7 @@ public class VpnTest {
|
||||
}));
|
||||
|
||||
// Remove the list of allowed packages.
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
|
||||
@ -422,7 +421,7 @@ public class VpnTest {
|
||||
|
||||
// Add the list of allowed packages.
|
||||
assertTrue(vpn.setAlwaysOnPackage(
|
||||
PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
|
||||
PKGS[0], true, Collections.singletonList(PKGS[1])));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop)
|
||||
}));
|
||||
@ -433,12 +432,12 @@ public class VpnTest {
|
||||
|
||||
// Try allowing a package with a comma, should be rejected.
|
||||
assertFalse(vpn.setAlwaysOnPackage(
|
||||
PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
|
||||
PKGS[0], true, Collections.singletonList("a.b,c.d")));
|
||||
|
||||
// Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
|
||||
// allowed 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"), mKeyStore));
|
||||
PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
|
||||
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
|
||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||
@ -525,22 +524,22 @@ public class VpnTest {
|
||||
.thenReturn(Collections.singletonList(resInfo));
|
||||
|
||||
// null package name should return false
|
||||
assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
|
||||
assertFalse(vpn.isAlwaysOnPackageSupported(null));
|
||||
|
||||
// Pre-N apps are not supported
|
||||
appInfo.targetSdkVersion = VERSION_CODES.M;
|
||||
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
|
||||
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||
|
||||
// N+ apps are supported by default
|
||||
appInfo.targetSdkVersion = VERSION_CODES.N;
|
||||
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
|
||||
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||
|
||||
// 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], mKeyStore));
|
||||
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -556,7 +555,7 @@ public class VpnTest {
|
||||
order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
|
||||
|
||||
// Start showing a notification for disconnected once always-on.
|
||||
vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
|
||||
vpn.setAlwaysOnPackage(PKGS[0], false, null);
|
||||
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
|
||||
|
||||
// Stop showing the notification once connected.
|
||||
@ -568,7 +567,7 @@ public class VpnTest {
|
||||
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
|
||||
|
||||
// Notification should be cleared after unsetting always-on package.
|
||||
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
|
||||
vpn.setAlwaysOnPackage(null, false, null);
|
||||
order.verify(mNotificationManager).cancel(anyString(), anyInt());
|
||||
}
|
||||
|
||||
@ -608,15 +607,13 @@ public class VpnTest {
|
||||
}
|
||||
|
||||
private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
|
||||
assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore));
|
||||
assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile));
|
||||
|
||||
// The profile should always be stored, whether or not consent has been previously granted.
|
||||
verify(mKeyStore)
|
||||
verify(mVpnProfileStore)
|
||||
.put(
|
||||
eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
|
||||
eq(mVpnProfile.encode()),
|
||||
eq(Process.SYSTEM_UID),
|
||||
eq(0));
|
||||
eq(mVpnProfile.encode()));
|
||||
|
||||
for (final String checkedOpStr : checkedOps) {
|
||||
verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
|
||||
@ -671,7 +668,7 @@ public class VpnTest {
|
||||
bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
|
||||
|
||||
try {
|
||||
vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore);
|
||||
vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
|
||||
fail("Expected IAE due to profile size");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
@ -684,7 +681,7 @@ public class VpnTest {
|
||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||
|
||||
try {
|
||||
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore);
|
||||
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
|
||||
fail("Expected SecurityException due to restricted user");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
@ -694,10 +691,10 @@ public class VpnTest {
|
||||
public void testDeleteVpnProfile() throws Exception {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||
|
||||
vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.deleteVpnProfile(TEST_VPN_PKG);
|
||||
|
||||
verify(mKeyStore)
|
||||
.delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID));
|
||||
verify(mVpnProfileStore)
|
||||
.remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -707,7 +704,7 @@ public class VpnTest {
|
||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||
|
||||
try {
|
||||
vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.deleteVpnProfile(TEST_VPN_PKG);
|
||||
fail("Expected SecurityException due to restricted user");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
@ -717,24 +714,24 @@ public class VpnTest {
|
||||
public void testGetVpnProfilePrivileged() throws Exception {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||
|
||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
.thenReturn(new VpnProfile("").encode());
|
||||
|
||||
vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.getVpnProfilePrivileged(TEST_VPN_PKG);
|
||||
|
||||
verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartVpnProfile() throws Exception {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||
|
||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
.thenReturn(mVpnProfile.encode());
|
||||
|
||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||
|
||||
verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
verify(mAppOps)
|
||||
.noteOpNoThrow(
|
||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
||||
@ -748,10 +745,10 @@ public class VpnTest {
|
||||
public void testStartVpnProfileVpnServicePreconsented() throws Exception {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
|
||||
|
||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
.thenReturn(mVpnProfile.encode());
|
||||
|
||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||
|
||||
// Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown.
|
||||
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
|
||||
@ -763,7 +760,7 @@ public class VpnTest {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||
|
||||
try {
|
||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||
fail("Expected failure due to no user consent");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
@ -780,22 +777,22 @@ public class VpnTest {
|
||||
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
|
||||
|
||||
// Keystore should never have been accessed.
|
||||
verify(mKeyStore, never()).get(any());
|
||||
verify(mVpnProfileStore, never()).get(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartVpnProfileMissingProfile() throws Exception {
|
||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||
|
||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
|
||||
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
|
||||
|
||||
try {
|
||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||
fail("Expected failure due to missing profile");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
|
||||
verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
|
||||
verify(mAppOps)
|
||||
.noteOpNoThrow(
|
||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
||||
@ -812,7 +809,7 @@ public class VpnTest {
|
||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||
|
||||
try {
|
||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
||||
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||
fail("Expected SecurityException due to restricted user");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
@ -938,9 +935,9 @@ public class VpnTest {
|
||||
}
|
||||
|
||||
private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
|
||||
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
|
||||
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null));
|
||||
|
||||
verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||
verify(mAppOps).setMode(
|
||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
|
||||
eq(AppOpsManager.MODE_ALLOWED));
|
||||
@ -963,11 +960,11 @@ public class VpnTest {
|
||||
final int uid = Process.myUid() + 1;
|
||||
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
|
||||
.thenReturn(uid);
|
||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||
.thenReturn(mVpnProfile.encode());
|
||||
|
||||
setAndVerifyAlwaysOnPackage(vpn, uid, false);
|
||||
assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
|
||||
assertTrue(vpn.startAlwaysOnVpn());
|
||||
|
||||
// TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
|
||||
// a subsequent CL.
|
||||
@ -984,7 +981,7 @@ public class VpnTest {
|
||||
InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
|
||||
lp.addRoute(defaultRoute);
|
||||
|
||||
vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp);
|
||||
vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
|
||||
return vpn;
|
||||
}
|
||||
|
||||
@ -1186,7 +1183,7 @@ public class VpnTest {
|
||||
.thenReturn(asUserContext);
|
||||
final TestLooper testLooper = new TestLooper();
|
||||
final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
|
||||
mNetd, userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
|
||||
mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
|
||||
verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
|
||||
provider -> provider.getName().contains("VpnNetworkProvider")
|
||||
));
|
||||
|
Loading…
x
Reference in New Issue
Block a user