Merge "Keystore 2.0: Make VPN Keystore 2.0 ready." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
f778d605b5
@@ -584,6 +584,7 @@ java_library {
|
|||||||
"android.security.apc-java",
|
"android.security.apc-java",
|
||||||
"android.security.authorization-java",
|
"android.security.authorization-java",
|
||||||
"android.security.usermanager-java",
|
"android.security.usermanager-java",
|
||||||
|
"android.security.vpnprofilestore-java",
|
||||||
"android.system.keystore2-V1-java",
|
"android.system.keystore2-V1-java",
|
||||||
"android.system.suspend.control.internal-java",
|
"android.system.suspend.control.internal-java",
|
||||||
"cameraprotosnano",
|
"cameraprotosnano",
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ import android.annotation.NonNull;
|
|||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.RequiresFeature;
|
import android.annotation.RequiresFeature;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Process;
|
|
||||||
import android.security.Credentials;
|
import android.security.Credentials;
|
||||||
import android.security.KeyStore;
|
|
||||||
import android.security.keystore.AndroidKeyStoreProvider;
|
|
||||||
|
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.net.VpnProfile;
|
import com.android.internal.net.VpnProfile;
|
||||||
@@ -35,7 +32,9 @@ import com.android.internal.net.VpnProfile;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.Key;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertificateEncodingException;
|
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 */
|
/** Prefix for when a Private Key is stored directly in the profile @hide */
|
||||||
public static final String PREFIX_INLINE = "INLINE:";
|
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 MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
|
||||||
private static final String EMPTY_CERT = "";
|
private static final String EMPTY_CERT = "";
|
||||||
|
|
||||||
@@ -430,32 +430,31 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) {
|
||||||
* Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance.
|
try {
|
||||||
*
|
final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
|
||||||
* <p>Redundant authentication information (not related to profile type) will be discarded.
|
keystore.load(null);
|
||||||
*
|
final Key key = keystore.getKey(alias, null);
|
||||||
* @hide
|
if (!(key instanceof PrivateKey)) {
|
||||||
*/
|
throw new IllegalStateException(
|
||||||
@NonNull
|
"Unexpected key type returned from android keystore.");
|
||||||
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
}
|
||||||
throws IOException, GeneralSecurityException {
|
return (PrivateKey) key;
|
||||||
return fromVpnProfile(profile, null);
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to load key from android keystore.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Ikev2VpnProfile from the given profile.
|
* Builds the Ikev2VpnProfile from the given profile.
|
||||||
*
|
*
|
||||||
* @param profile the source VpnProfile to build from
|
* @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
|
* @return The IKEv2/IPsec VPN profile
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static Ikev2VpnProfile fromVpnProfile(
|
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
||||||
@NonNull VpnProfile profile, @Nullable KeyStore keyStore)
|
throws GeneralSecurityException {
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
|
final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
|
||||||
builder.setProxy(profile.proxy);
|
builder.setProxy(profile.proxy);
|
||||||
builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
|
builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
|
||||||
@@ -479,12 +478,9 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
|||||||
case TYPE_IKEV2_IPSEC_RSA:
|
case TYPE_IKEV2_IPSEC_RSA:
|
||||||
final PrivateKey key;
|
final PrivateKey key;
|
||||||
if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
|
if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
|
||||||
Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
|
|
||||||
|
|
||||||
final String alias =
|
final String alias =
|
||||||
profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
|
profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
|
||||||
key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
|
key = getPrivateKeyFromAndroidKeystore(alias);
|
||||||
keyStore, alias, Process.myUid());
|
|
||||||
} else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
|
} else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
|
||||||
key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
|
key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
|
||||||
} else {
|
} 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.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.security.Credentials;
|
import android.security.Credentials;
|
||||||
import android.security.KeyStore;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
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.DumpUtils;
|
||||||
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
import com.android.server.connectivity.Vpn;
|
import com.android.server.connectivity.Vpn;
|
||||||
|
import com.android.server.connectivity.VpnProfileStore;
|
||||||
import com.android.server.net.LockdownVpnTracker;
|
import com.android.server.net.LockdownVpnTracker;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@@ -83,7 +83,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
private final Dependencies mDeps;
|
private final Dependencies mDeps;
|
||||||
|
|
||||||
private final ConnectivityManager mCm;
|
private final ConnectivityManager mCm;
|
||||||
private final KeyStore mKeyStore;
|
private final VpnProfileStore mVpnProfileStore;
|
||||||
private final INetworkManagementService mNMS;
|
private final INetworkManagementService mNMS;
|
||||||
private final INetd mNetd;
|
private final INetd mNetd;
|
||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
@@ -114,9 +114,9 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
return new HandlerThread("VpnManagerService");
|
return new HandlerThread("VpnManagerService");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the KeyStore instance to be used by this class. */
|
/** Return the VpnProfileStore to be used by this class */
|
||||||
public KeyStore getKeyStore() {
|
public VpnProfileStore getVpnProfileStore() {
|
||||||
return KeyStore.getInstance();
|
return new VpnProfileStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
public INetd getNetd() {
|
public INetd getNetd() {
|
||||||
@@ -135,7 +135,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
mHandlerThread = mDeps.makeHandlerThread();
|
mHandlerThread = mDeps.makeHandlerThread();
|
||||||
mHandlerThread.start();
|
mHandlerThread.start();
|
||||||
mHandler = mHandlerThread.getThreadHandler();
|
mHandler = mHandlerThread.getThreadHandler();
|
||||||
mKeyStore = mDeps.getKeyStore();
|
mVpnProfileStore = mDeps.getVpnProfileStore();
|
||||||
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
|
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
|
||||||
mCm = mContext.getSystemService(ConnectivityManager.class);
|
mCm = mContext.getSystemService(ConnectivityManager.class);
|
||||||
mNMS = mDeps.getINetworkManagementService();
|
mNMS = mDeps.getINetworkManagementService();
|
||||||
@@ -289,7 +289,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
|
public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
|
||||||
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||||
synchronized (mVpns) {
|
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) {
|
public void deleteVpnProfile(@NonNull String packageName) {
|
||||||
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||||
synchronized (mVpns) {
|
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());
|
final int user = UserHandle.getUserId(mDeps.getCallingUid());
|
||||||
synchronized (mVpns) {
|
synchronized (mVpns) {
|
||||||
throwIfLockdownEnabled();
|
throwIfLockdownEnabled();
|
||||||
mVpns.get(user).startVpnProfile(packageName, mKeyStore);
|
mVpns.get(user).startVpnProfile(packageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +358,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
}
|
}
|
||||||
synchronized (mVpns) {
|
synchronized (mVpns) {
|
||||||
throwIfLockdownEnabled();
|
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() {
|
private boolean isLockdownVpnEnabled() {
|
||||||
return mKeyStore.contains(Credentials.LOCKDOWN_VPN);
|
return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -417,14 +417,14 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
|
byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN);
|
||||||
if (profileTag == null) {
|
if (profileTag == null) {
|
||||||
loge("Lockdown VPN configured but cannot be read from keystore");
|
loge("Lockdown VPN configured but cannot be read from keystore");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String profileName = new String(profileTag);
|
String profileName = new String(profileTag);
|
||||||
final VpnProfile profile = VpnProfile.decode(
|
final VpnProfile profile = VpnProfile.decode(
|
||||||
profileName, mKeyStore.get(Credentials.VPN + profileName));
|
profileName, mVpnProfileStore.get(Credentials.VPN + profileName));
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
loge("Lockdown VPN configured invalid profile " + profileName);
|
loge("Lockdown VPN configured invalid profile " + profileName);
|
||||||
setLockdownTracker(null);
|
setLockdownTracker(null);
|
||||||
@@ -437,7 +437,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setLockdownTracker(
|
setLockdownTracker(
|
||||||
new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile));
|
new LockdownVpnTracker(mContext, mHandler, vpn, profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
return false;
|
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");
|
logw("User " + userId + " has no Vpn configuration");
|
||||||
return false;
|
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");
|
logw("User " + userId + " has no Vpn configuration");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) {
|
if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!startAlwaysOnVpn(userId)) {
|
if (!startAlwaysOnVpn(userId)) {
|
||||||
vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
|
vpn.setAlwaysOnPackage(null, false, null);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,7 +705,8 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
loge("Starting user already has a VPN");
|
loge("Starting user already has a VPN");
|
||||||
return;
|
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);
|
mVpns.put(userId, userVpn);
|
||||||
if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
|
if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
|
||||||
updateLockdownVpn();
|
updateLockdownVpn();
|
||||||
@@ -777,7 +778,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
|
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
|
||||||
log("Restarting always-on VPN package " + packageName + " for user "
|
log("Restarting always-on VPN package " + packageName + " for user "
|
||||||
+ userId);
|
+ userId);
|
||||||
vpn.startAlwaysOnVpn(mKeyStore);
|
vpn.startAlwaysOnVpn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,7 +799,7 @@ public class VpnManagerService extends IVpnManager.Stub {
|
|||||||
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
|
if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
|
||||||
log("Removing always-on VPN package " + packageName + " for user "
|
log("Removing always-on VPN package " + packageName + " for user "
|
||||||
+ userId);
|
+ 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) {
|
if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
|
||||||
final long ident = Binder.clearCallingIdentity();
|
final long ident = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
mKeyStore.delete(Credentials.LOCKDOWN_VPN);
|
mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
|
||||||
mLockdownEnabled = false;
|
mLockdownEnabled = false;
|
||||||
setLockdownTracker(null);
|
setLockdownTracker(null);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ import android.os.UserHandle;
|
|||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.security.Credentials;
|
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.text.TextUtils;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -132,6 +137,12 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.GeneralSecurityException;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -157,6 +168,7 @@ public class Vpn {
|
|||||||
private static final String TAG = "Vpn";
|
private static final String TAG = "Vpn";
|
||||||
private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
|
private static final String VPN_PROVIDER_NAME_BASE = "VpnNetworkProvider:";
|
||||||
private static final boolean LOGD = true;
|
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
|
// 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.
|
// the device idle allowlist during service launch and VPN bootstrap.
|
||||||
@@ -216,6 +228,13 @@ public class Vpn {
|
|||||||
private final Ikev2SessionCreator mIkev2SessionCreator;
|
private final Ikev2SessionCreator mIkev2SessionCreator;
|
||||||
private final UserManager mUserManager;
|
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
|
* Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
|
||||||
* only applies to {@link VpnService} connections.
|
* only applies to {@link VpnService} connections.
|
||||||
@@ -393,24 +412,25 @@ public class Vpn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
|
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
|
||||||
@UserIdInt int userId, @NonNull KeyStore keyStore) {
|
@UserIdInt int userId, VpnProfileStore vpnProfileStore) {
|
||||||
this(looper, context, new Dependencies(), netService, netd, userId, keyStore,
|
this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore,
|
||||||
new SystemServices(context), new Ikev2SessionCreator());
|
new SystemServices(context), new Ikev2SessionCreator());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public Vpn(Looper looper, Context context, Dependencies deps,
|
public Vpn(Looper looper, Context context, Dependencies deps,
|
||||||
INetworkManagementService netService, INetd netd, @UserIdInt int userId,
|
INetworkManagementService netService, INetd netd, @UserIdInt int userId,
|
||||||
@NonNull KeyStore keyStore) {
|
VpnProfileStore vpnProfileStore) {
|
||||||
this(looper, context, deps, netService, netd, userId, keyStore,
|
this(looper, context, deps, netService, netd, userId, vpnProfileStore,
|
||||||
new SystemServices(context), new Ikev2SessionCreator());
|
new SystemServices(context), new Ikev2SessionCreator());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected Vpn(Looper looper, Context context, Dependencies deps,
|
protected Vpn(Looper looper, Context context, Dependencies deps,
|
||||||
INetworkManagementService netService, INetd netd,
|
INetworkManagementService netService, INetd netd,
|
||||||
int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
|
int userId, VpnProfileStore vpnProfileStore, SystemServices systemServices,
|
||||||
Ikev2SessionCreator ikev2SessionCreator) {
|
Ikev2SessionCreator ikev2SessionCreator) {
|
||||||
|
mVpnProfileStore = vpnProfileStore;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||||
mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
|
mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
|
||||||
@@ -446,7 +466,7 @@ public class Vpn {
|
|||||||
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||||
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
|
mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE));
|
||||||
|
|
||||||
loadAlwaysOnPackage(keyStore);
|
loadAlwaysOnPackage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -567,11 +587,9 @@ public class Vpn {
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param packageName the canonical package name of the VPN app
|
* @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
|
* @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();
|
enforceSettingsPermission();
|
||||||
|
|
||||||
if (packageName == null) {
|
if (packageName == null) {
|
||||||
@@ -580,7 +598,7 @@ public class Vpn {
|
|||||||
|
|
||||||
final long oldId = Binder.clearCallingIdentity();
|
final long oldId = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
if (getVpnProfilePrivileged(packageName, keyStore) != null) {
|
if (getVpnProfilePrivileged(packageName) != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -632,17 +650,15 @@ public class Vpn {
|
|||||||
* @param packageName the package to designate as always-on VPN supplier.
|
* @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 lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
||||||
* @param lockdownAllowlist packages to be allowed from lockdown.
|
* @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.
|
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean setAlwaysOnPackage(
|
public synchronized boolean setAlwaysOnPackage(
|
||||||
@Nullable String packageName,
|
@Nullable String packageName,
|
||||||
boolean lockdown,
|
boolean lockdown,
|
||||||
@Nullable List<String> lockdownAllowlist,
|
@Nullable List<String> lockdownAllowlist) {
|
||||||
@NonNull KeyStore keyStore) {
|
|
||||||
enforceControlPermissionOrInternalCaller();
|
enforceControlPermissionOrInternalCaller();
|
||||||
|
|
||||||
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist, keyStore)) {
|
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
|
||||||
saveAlwaysOnPackage();
|
saveAlwaysOnPackage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -659,13 +675,12 @@ public class Vpn {
|
|||||||
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
* @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
|
* @param lockdownAllowlist packages to be allowed to bypass 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.
|
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private boolean setAlwaysOnPackageInternal(
|
private boolean setAlwaysOnPackageInternal(
|
||||||
@Nullable String packageName, boolean lockdown,
|
@Nullable String packageName, boolean lockdown,
|
||||||
@Nullable List<String> lockdownAllowlist, @NonNull KeyStore keyStore) {
|
@Nullable List<String> lockdownAllowlist) {
|
||||||
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
|
if (VpnConfig.LEGACY_VPN.equals(packageName)) {
|
||||||
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
|
Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
|
||||||
return false;
|
return false;
|
||||||
@@ -684,7 +699,7 @@ public class Vpn {
|
|||||||
final VpnProfile profile;
|
final VpnProfile profile;
|
||||||
final long oldId = Binder.clearCallingIdentity();
|
final long oldId = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
profile = getVpnProfilePrivileged(packageName, keyStore);
|
profile = getVpnProfilePrivileged(packageName);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(oldId);
|
Binder.restoreCallingIdentity(oldId);
|
||||||
}
|
}
|
||||||
@@ -759,7 +774,7 @@ public class Vpn {
|
|||||||
|
|
||||||
/** Load the always-on package and lockdown config from Settings. */
|
/** Load the always-on package and lockdown config from Settings. */
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
|
private void loadAlwaysOnPackage() {
|
||||||
final long token = Binder.clearCallingIdentity();
|
final long token = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
|
final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
|
||||||
@@ -771,7 +786,7 @@ public class Vpn {
|
|||||||
final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
|
final List<String> allowedPackages = TextUtils.isEmpty(allowlistString)
|
||||||
? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
|
? Collections.emptyList() : Arrays.asList(allowlistString.split(","));
|
||||||
setAlwaysOnPackageInternal(
|
setAlwaysOnPackageInternal(
|
||||||
alwaysOnPackage, alwaysOnLockdown, allowedPackages, keyStore);
|
alwaysOnPackage, alwaysOnLockdown, allowedPackages);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(token);
|
Binder.restoreCallingIdentity(token);
|
||||||
}
|
}
|
||||||
@@ -780,11 +795,10 @@ public class Vpn {
|
|||||||
/**
|
/**
|
||||||
* Starts the currently selected always-on 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
|
* @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(@NonNull KeyStore keyStore) {
|
public boolean startAlwaysOnVpn() {
|
||||||
final String alwaysOnPackage;
|
final String alwaysOnPackage;
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
alwaysOnPackage = getAlwaysOnPackage();
|
alwaysOnPackage = getAlwaysOnPackage();
|
||||||
@@ -793,8 +807,8 @@ public class Vpn {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Remove always-on VPN if it's not supported.
|
// Remove always-on VPN if it's not supported.
|
||||||
if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
|
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
|
||||||
setAlwaysOnPackage(null, false, null, keyStore);
|
setAlwaysOnPackage(null, false, null);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Skip if the service is already established. This isn't bulletproof: it's not bound
|
// 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();
|
final long oldId = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
// Prefer VPN profiles, if any exist.
|
// Prefer VPN profiles, if any exist.
|
||||||
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
|
VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage);
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
startVpnProfilePrivileged(profile, alwaysOnPackage,
|
startVpnProfilePrivileged(profile, alwaysOnPackage);
|
||||||
null /* keyStore for private key retrieval - unneeded */);
|
|
||||||
|
|
||||||
// If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
|
// If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
|
||||||
// correctly parsed, and the VPN has started running in a different thread. The only
|
// 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.
|
* secondary thread to perform connection work, returning quickly.
|
||||||
*
|
*
|
||||||
* Should only be called to respond to Binder requests as this enforces caller permission. Use
|
* 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).
|
* 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) {
|
LinkProperties egress) {
|
||||||
enforceControlPermission();
|
enforceControlPermission();
|
||||||
final long token = Binder.clearCallingIdentity();
|
final long token = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
startLegacyVpnPrivileged(profile, keyStore, underlying, egress);
|
startLegacyVpnPrivileged(profile, underlying, egress);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(token);
|
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.
|
* check permissions under the assumption that the caller is the system.
|
||||||
*
|
*
|
||||||
* Callers are responsible for checking permissions if needed.
|
* 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) {
|
@Nullable Network underlying, @NonNull LinkProperties egress) {
|
||||||
UserInfo user = mUserManager.getUserInfo(mUserId);
|
UserInfo user = mUserManager.getUserInfo(mUserId);
|
||||||
if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
|
if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
|
||||||
@@ -2050,18 +2119,27 @@ public class Vpn {
|
|||||||
String userCert = "";
|
String userCert = "";
|
||||||
String caCert = "";
|
String caCert = "";
|
||||||
String serverCert = "";
|
String serverCert = "";
|
||||||
if (!profile.ipsecUserCert.isEmpty()) {
|
|
||||||
privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
|
try {
|
||||||
byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
|
final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER);
|
||||||
userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
keystore.load(null);
|
||||||
}
|
if (!profile.ipsecUserCert.isEmpty()) {
|
||||||
if (!profile.ipsecCaCert.isEmpty()) {
|
privateKey = profile.ipsecUserCert;
|
||||||
byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
|
final Certificate cert = keystore.getCertificate(profile.ipsecUserCert);
|
||||||
caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
userCert = (cert == null) ? null
|
||||||
}
|
: new String(Credentials.convertToPem(cert), StandardCharsets.UTF_8);
|
||||||
if (!profile.ipsecServerCert.isEmpty()) {
|
}
|
||||||
byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
|
if (!profile.ipsecCaCert.isEmpty()) {
|
||||||
serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
|
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) {
|
if (userCert == null || caCert == null || serverCert == null) {
|
||||||
throw new IllegalStateException("Cannot load credentials");
|
throw new IllegalStateException("Cannot load credentials");
|
||||||
@@ -2082,7 +2160,7 @@ public class Vpn {
|
|||||||
|
|
||||||
// Start VPN profile
|
// Start VPN profile
|
||||||
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
||||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
|
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
|
||||||
return;
|
return;
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
|
||||||
// Ikev2VpnProfiles expect a base64-encoded preshared key.
|
// Ikev2VpnProfiles expect a base64-encoded preshared key.
|
||||||
@@ -2091,7 +2169,7 @@ public class Vpn {
|
|||||||
|
|
||||||
// Start VPN profile
|
// Start VPN profile
|
||||||
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
profile.setAllowedAlgorithms(Ikev2VpnProfile.DEFAULT_ALGORITHMS);
|
||||||
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
|
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
|
||||||
return;
|
return;
|
||||||
case VpnProfile.TYPE_L2TP_IPSEC_PSK:
|
case VpnProfile.TYPE_L2TP_IPSEC_PSK:
|
||||||
racoon = new String[] {
|
racoon = new String[] {
|
||||||
@@ -2101,8 +2179,8 @@ public class Vpn {
|
|||||||
break;
|
break;
|
||||||
case VpnProfile.TYPE_L2TP_IPSEC_RSA:
|
case VpnProfile.TYPE_L2TP_IPSEC_RSA:
|
||||||
racoon = new String[] {
|
racoon = new String[] {
|
||||||
iface, profile.server, "udprsa", privateKey, userCert,
|
iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey),
|
||||||
caCert, serverCert, "1701",
|
userCert, caCert, serverCert, "1701",
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
|
case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
|
||||||
@@ -2113,8 +2191,8 @@ public class Vpn {
|
|||||||
break;
|
break;
|
||||||
case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
|
case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
|
||||||
racoon = new String[] {
|
racoon = new String[] {
|
||||||
iface, profile.server, "xauthrsa", privateKey, userCert,
|
iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey),
|
||||||
caCert, serverCert, profile.username, profile.password, "", gateway,
|
userCert, caCert, serverCert, profile.username, profile.password, "", gateway,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
|
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 packageName the package name of the app provisioning this profile
|
||||||
* @param profile the profile to be stored and provisioned
|
* @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
|
* @returns whether or not the app has already been granted user consent
|
||||||
*/
|
*/
|
||||||
public synchronized boolean provisionVpnProfile(
|
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(packageName, "No package name provided");
|
||||||
checkNotNull(profile, "No profile provided");
|
checkNotNull(profile, "No profile provided");
|
||||||
checkNotNull(keyStore, "KeyStore missing");
|
|
||||||
|
|
||||||
verifyCallingUidAndPackage(packageName);
|
verifyCallingUidAndPackage(packageName);
|
||||||
enforceNotRestrictedUser();
|
enforceNotRestrictedUser();
|
||||||
@@ -3075,11 +3151,9 @@ public class Vpn {
|
|||||||
// Permissions checked during startVpnProfile()
|
// Permissions checked during startVpnProfile()
|
||||||
Binder.withCleanCallingIdentity(
|
Binder.withCleanCallingIdentity(
|
||||||
() -> {
|
() -> {
|
||||||
keyStore.put(
|
getVpnProfileStore().put(
|
||||||
getProfileNameForPackage(packageName),
|
getProfileNameForPackage(packageName),
|
||||||
encodedProfile,
|
encodedProfile);
|
||||||
Process.SYSTEM_UID,
|
|
||||||
0 /* flags */);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
|
// 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.
|
* Deletes an app-provisioned VPN profile.
|
||||||
*
|
*
|
||||||
* @param packageName the package name of the app provisioning this 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(
|
public synchronized void deleteVpnProfile(
|
||||||
@NonNull String packageName, @NonNull KeyStore keyStore) {
|
@NonNull String packageName) {
|
||||||
checkNotNull(packageName, "No package name provided");
|
checkNotNull(packageName, "No package name provided");
|
||||||
checkNotNull(keyStore, "KeyStore missing");
|
|
||||||
|
|
||||||
verifyCallingUidAndPackage(packageName);
|
verifyCallingUidAndPackage(packageName);
|
||||||
enforceNotRestrictedUser();
|
enforceNotRestrictedUser();
|
||||||
@@ -3114,13 +3186,13 @@ public class Vpn {
|
|||||||
if (isCurrentIkev2VpnLocked(packageName)) {
|
if (isCurrentIkev2VpnLocked(packageName)) {
|
||||||
if (mAlwaysOn) {
|
if (mAlwaysOn) {
|
||||||
// Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
|
// Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
|
||||||
setAlwaysOnPackage(null, false, null, keyStore);
|
setAlwaysOnPackage(null, false, null);
|
||||||
} else {
|
} else {
|
||||||
prepareInternal(VpnConfig.LEGACY_VPN);
|
prepareInternal(VpnConfig.LEGACY_VPN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
|
getVpnProfileStore().remove(getProfileNameForPackage(packageName));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3132,13 +3204,13 @@ public class Vpn {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Nullable
|
@Nullable
|
||||||
VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) {
|
VpnProfile getVpnProfilePrivileged(@NonNull String packageName) {
|
||||||
if (!mDeps.isCallerSystem()) {
|
if (!mDeps.isCallerSystem()) {
|
||||||
Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID ");
|
Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID ");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName));
|
final byte[] encoded = getVpnProfileStore().get(getProfileNameForPackage(packageName));
|
||||||
if (encoded == null) return null;
|
if (encoded == null) return null;
|
||||||
|
|
||||||
return VpnProfile.decode("" /* Key unused */, encoded);
|
return VpnProfile.decode("" /* Key unused */, encoded);
|
||||||
@@ -3152,12 +3224,10 @@ public class Vpn {
|
|||||||
* will not match during appop checks.
|
* will not match during appop checks.
|
||||||
*
|
*
|
||||||
* @param packageName the package name of the app provisioning this profile
|
* @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(
|
public synchronized void startVpnProfile(
|
||||||
@NonNull String packageName, @NonNull KeyStore keyStore) {
|
@NonNull String packageName) {
|
||||||
checkNotNull(packageName, "No package name provided");
|
checkNotNull(packageName, "No package name provided");
|
||||||
checkNotNull(keyStore, "KeyStore missing");
|
|
||||||
|
|
||||||
enforceNotRestrictedUser();
|
enforceNotRestrictedUser();
|
||||||
|
|
||||||
@@ -3168,18 +3238,17 @@ public class Vpn {
|
|||||||
|
|
||||||
Binder.withCleanCallingIdentity(
|
Binder.withCleanCallingIdentity(
|
||||||
() -> {
|
() -> {
|
||||||
final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore);
|
final VpnProfile profile = getVpnProfilePrivileged(packageName);
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
throw new IllegalArgumentException("No profile found for " + packageName);
|
throw new IllegalArgumentException("No profile found for " + packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
startVpnProfilePrivileged(profile, packageName,
|
startVpnProfilePrivileged(profile, packageName);
|
||||||
null /* keyStore for private key retrieval - unneeded */);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void startVpnProfilePrivileged(
|
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(),
|
// 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
|
// 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
|
// 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_PSK:
|
||||||
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
|
||||||
mVpnRunner =
|
mVpnRunner =
|
||||||
new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
|
new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
|
||||||
mVpnRunner.start();
|
mVpnRunner.start();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -3218,7 +3287,7 @@ public class Vpn {
|
|||||||
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
|
Log.d(TAG, "Unknown VPN profile type: " + profile.type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException | GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
// Reset mConfig
|
// Reset mConfig
|
||||||
mConfig = null;
|
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.NetworkInfo;
|
||||||
import android.net.NetworkRequest;
|
import android.net.NetworkRequest;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.security.KeyStore;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -63,7 +62,6 @@ public class LockdownVpnTracker {
|
|||||||
@NonNull private final Handler mHandler;
|
@NonNull private final Handler mHandler;
|
||||||
@NonNull private final Vpn mVpn;
|
@NonNull private final Vpn mVpn;
|
||||||
@NonNull private final VpnProfile mProfile;
|
@NonNull private final VpnProfile mProfile;
|
||||||
@NonNull private final KeyStore mKeyStore;
|
|
||||||
|
|
||||||
@NonNull private final Object mStateLock = new Object();
|
@NonNull private final Object mStateLock = new Object();
|
||||||
|
|
||||||
@@ -132,7 +130,6 @@ public class LockdownVpnTracker {
|
|||||||
|
|
||||||
public LockdownVpnTracker(@NonNull Context context,
|
public LockdownVpnTracker(@NonNull Context context,
|
||||||
@NonNull Handler handler,
|
@NonNull Handler handler,
|
||||||
@NonNull KeyStore keyStore,
|
|
||||||
@NonNull Vpn vpn,
|
@NonNull Vpn vpn,
|
||||||
@NonNull VpnProfile profile) {
|
@NonNull VpnProfile profile) {
|
||||||
mContext = Objects.requireNonNull(context);
|
mContext = Objects.requireNonNull(context);
|
||||||
@@ -140,7 +137,6 @@ public class LockdownVpnTracker {
|
|||||||
mHandler = Objects.requireNonNull(handler);
|
mHandler = Objects.requireNonNull(handler);
|
||||||
mVpn = Objects.requireNonNull(vpn);
|
mVpn = Objects.requireNonNull(vpn);
|
||||||
mProfile = Objects.requireNonNull(profile);
|
mProfile = Objects.requireNonNull(profile);
|
||||||
mKeyStore = Objects.requireNonNull(keyStore);
|
|
||||||
mNotificationManager = mContext.getSystemService(NotificationManager.class);
|
mNotificationManager = mContext.getSystemService(NotificationManager.class);
|
||||||
|
|
||||||
final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
|
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
|
// 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
|
// (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have
|
||||||
// changed to match the new default network (e.g., cell).
|
// changed to match the new default network (e.g., cell).
|
||||||
mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp);
|
mVpn.startLegacyVpnPrivileged(mProfile, network, egressProp);
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
mAcceptedEgressIface = null;
|
mAcceptedEgressIface = null;
|
||||||
Log.e(TAG, "Failed to start VPN", e);
|
Log.e(TAG, "Failed to start VPN", e);
|
||||||
|
|||||||
@@ -250,7 +250,6 @@ import android.os.UserHandle;
|
|||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.security.Credentials;
|
import android.security.Credentials;
|
||||||
import android.security.KeyStore;
|
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.telephony.data.EpsBearerQosSessionAttributes;
|
import android.telephony.data.EpsBearerQosSessionAttributes;
|
||||||
@@ -282,6 +281,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy
|
|||||||
import com.android.server.connectivity.ProxyTracker;
|
import com.android.server.connectivity.ProxyTracker;
|
||||||
import com.android.server.connectivity.QosCallbackTracker;
|
import com.android.server.connectivity.QosCallbackTracker;
|
||||||
import com.android.server.connectivity.Vpn;
|
import com.android.server.connectivity.Vpn;
|
||||||
|
import com.android.server.connectivity.VpnProfileStore;
|
||||||
import com.android.server.net.NetworkPinner;
|
import com.android.server.net.NetworkPinner;
|
||||||
import com.android.server.net.NetworkPolicyManagerInternal;
|
import com.android.server.net.NetworkPolicyManagerInternal;
|
||||||
import com.android.testutils.ExceptionUtils;
|
import com.android.testutils.ExceptionUtils;
|
||||||
@@ -440,7 +440,7 @@ public class ConnectivityServiceTest {
|
|||||||
@Mock MockableSystemProperties mSystemProperties;
|
@Mock MockableSystemProperties mSystemProperties;
|
||||||
@Mock EthernetManager mEthernetManager;
|
@Mock EthernetManager mEthernetManager;
|
||||||
@Mock NetworkPolicyManager mNetworkPolicyManager;
|
@Mock NetworkPolicyManager mNetworkPolicyManager;
|
||||||
@Mock KeyStore mKeyStore;
|
@Mock VpnProfileStore mVpnProfileStore;
|
||||||
@Mock SystemConfigManager mSystemConfigManager;
|
@Mock SystemConfigManager mSystemConfigManager;
|
||||||
|
|
||||||
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
|
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
|
||||||
@@ -1124,7 +1124,7 @@ public class ConnectivityServiceTest {
|
|||||||
return mDeviceIdleInternal;
|
return mDeviceIdleInternal;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mNetworkManagementService, mMockNetd, userId, mKeyStore);
|
mNetworkManagementService, mMockNetd, userId, mVpnProfileStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUids(Set<UidRange> uids) {
|
public void setUids(Set<UidRange> uids) {
|
||||||
@@ -1303,8 +1303,9 @@ public class ConnectivityServiceTest {
|
|||||||
return mVMSHandlerThread;
|
return mVMSHandlerThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyStore getKeyStore() {
|
@Override
|
||||||
return mKeyStore;
|
public VpnProfileStore getVpnProfileStore() {
|
||||||
|
return mVpnProfileStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public INetd getNetd() {
|
public INetd getNetd() {
|
||||||
@@ -7509,8 +7510,7 @@ public class ConnectivityServiceTest {
|
|||||||
private void setupLegacyLockdownVpn() {
|
private void setupLegacyLockdownVpn() {
|
||||||
final String profileName = "testVpnProfile";
|
final String profileName = "testVpnProfile";
|
||||||
final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
|
final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
|
||||||
when(mKeyStore.contains(Credentials.LOCKDOWN_VPN)).thenReturn(true);
|
when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
|
||||||
when(mKeyStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag);
|
|
||||||
|
|
||||||
final VpnProfile profile = new VpnProfile(profileName);
|
final VpnProfile profile = new VpnProfile(profileName);
|
||||||
profile.name = "My VPN";
|
profile.name = "My VPN";
|
||||||
@@ -7518,7 +7518,7 @@ public class ConnectivityServiceTest {
|
|||||||
profile.dnsServers = "8.8.8.8";
|
profile.dnsServers = "8.8.8.8";
|
||||||
profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
|
profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK;
|
||||||
final byte[] encodedProfile = profile.encode();
|
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 {
|
private void establishLegacyLockdownVpn(Network underlying) throws Exception {
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ import android.os.UserManager;
|
|||||||
import android.os.test.TestLooper;
|
import android.os.test.TestLooper;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.security.Credentials;
|
import android.security.Credentials;
|
||||||
import android.security.KeyStore;
|
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Range;
|
import android.util.Range;
|
||||||
@@ -196,7 +195,7 @@ public class VpnTest {
|
|||||||
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
|
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
|
||||||
@Mock private ConnectivityManager mConnectivityManager;
|
@Mock private ConnectivityManager mConnectivityManager;
|
||||||
@Mock private IpSecService mIpSecService;
|
@Mock private IpSecService mIpSecService;
|
||||||
@Mock private KeyStore mKeyStore;
|
@Mock private VpnProfileStore mVpnProfileStore;
|
||||||
private final VpnProfile mVpnProfile;
|
private final VpnProfile mVpnProfile;
|
||||||
|
|
||||||
private IpSecManager mIpSecManager;
|
private IpSecManager mIpSecManager;
|
||||||
@@ -333,17 +332,17 @@ public class VpnTest {
|
|||||||
assertFalse(vpn.getLockdown());
|
assertFalse(vpn.getLockdown());
|
||||||
|
|
||||||
// Set always-on without lockdown.
|
// 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());
|
assertTrue(vpn.getAlwaysOn());
|
||||||
assertFalse(vpn.getLockdown());
|
assertFalse(vpn.getLockdown());
|
||||||
|
|
||||||
// Set always-on with lockdown.
|
// 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.getAlwaysOn());
|
||||||
assertTrue(vpn.getLockdown());
|
assertTrue(vpn.getLockdown());
|
||||||
|
|
||||||
// Remove always-on configuration.
|
// Remove always-on configuration.
|
||||||
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
|
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
|
||||||
assertFalse(vpn.getAlwaysOn());
|
assertFalse(vpn.getAlwaysOn());
|
||||||
assertFalse(vpn.getLockdown());
|
assertFalse(vpn.getLockdown());
|
||||||
}
|
}
|
||||||
@@ -354,17 +353,17 @@ public class VpnTest {
|
|||||||
final UidRange user = PRI_USER_RANGE;
|
final UidRange user = PRI_USER_RANGE;
|
||||||
|
|
||||||
// Set always-on without lockdown.
|
// 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.
|
// 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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Switch to another app.
|
// 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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
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.
|
// Set always-on with lockdown and allow app PKGS[2] from lockdown.
|
||||||
assertTrue(vpn.setAlwaysOnPackage(
|
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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||||
}));
|
}));
|
||||||
// Change allowed app list to PKGS[3].
|
// Change allowed app list to PKGS[3].
|
||||||
assertTrue(vpn.setAlwaysOnPackage(
|
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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
|
||||||
}));
|
}));
|
||||||
@@ -400,7 +399,7 @@ public class VpnTest {
|
|||||||
|
|
||||||
// Change the VPN app.
|
// Change the VPN app.
|
||||||
assertTrue(vpn.setAlwaysOnPackage(
|
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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 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.
|
// 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[] {
|
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[0] + 1, user.start + PKG_UIDS[3] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
|
new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
|
||||||
@@ -422,7 +421,7 @@ public class VpnTest {
|
|||||||
|
|
||||||
// Add the list of allowed packages.
|
// Add the list of allowed packages.
|
||||||
assertTrue(vpn.setAlwaysOnPackage(
|
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[] {
|
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop)
|
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.
|
// Try allowing a package with a comma, should be rejected.
|
||||||
assertFalse(vpn.setAlwaysOnPackage(
|
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.
|
// 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].
|
// allowed package should change from PGKS[1] to PKGS[2].
|
||||||
assertTrue(vpn.setAlwaysOnPackage(
|
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[] {
|
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[0] + 1, user.start + PKG_UIDS[1] - 1),
|
||||||
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
|
||||||
@@ -525,22 +524,22 @@ public class VpnTest {
|
|||||||
.thenReturn(Collections.singletonList(resInfo));
|
.thenReturn(Collections.singletonList(resInfo));
|
||||||
|
|
||||||
// null package name should return false
|
// null package name should return false
|
||||||
assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
|
assertFalse(vpn.isAlwaysOnPackageSupported(null));
|
||||||
|
|
||||||
// Pre-N apps are not supported
|
// Pre-N apps are not supported
|
||||||
appInfo.targetSdkVersion = VERSION_CODES.M;
|
appInfo.targetSdkVersion = VERSION_CODES.M;
|
||||||
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
|
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||||
|
|
||||||
// N+ apps are supported by default
|
// N+ apps are supported by default
|
||||||
appInfo.targetSdkVersion = VERSION_CODES.N;
|
appInfo.targetSdkVersion = VERSION_CODES.N;
|
||||||
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
|
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||||
|
|
||||||
// Apps that opt out explicitly are not supported
|
// Apps that opt out explicitly are not supported
|
||||||
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
|
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
|
||||||
Bundle metaData = new Bundle();
|
Bundle metaData = new Bundle();
|
||||||
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
|
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
|
||||||
svcInfo.metaData = metaData;
|
svcInfo.metaData = metaData;
|
||||||
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
|
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -556,7 +555,7 @@ public class VpnTest {
|
|||||||
order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
|
order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
|
||||||
|
|
||||||
// Start showing a notification for disconnected once always-on.
|
// 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());
|
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
|
||||||
|
|
||||||
// Stop showing the notification once connected.
|
// Stop showing the notification once connected.
|
||||||
@@ -568,7 +567,7 @@ public class VpnTest {
|
|||||||
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
|
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
|
||||||
|
|
||||||
// Notification should be cleared after unsetting always-on package.
|
// 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());
|
order.verify(mNotificationManager).cancel(anyString(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,15 +607,13 @@ public class VpnTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
|
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.
|
// The profile should always be stored, whether or not consent has been previously granted.
|
||||||
verify(mKeyStore)
|
verify(mVpnProfileStore)
|
||||||
.put(
|
.put(
|
||||||
eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
|
eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
|
||||||
eq(mVpnProfile.encode()),
|
eq(mVpnProfile.encode()));
|
||||||
eq(Process.SYSTEM_UID),
|
|
||||||
eq(0));
|
|
||||||
|
|
||||||
for (final String checkedOpStr : checkedOps) {
|
for (final String checkedOpStr : checkedOps) {
|
||||||
verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
|
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]);
|
bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore);
|
vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
|
||||||
fail("Expected IAE due to profile size");
|
fail("Expected IAE due to profile size");
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
}
|
}
|
||||||
@@ -684,7 +681,7 @@ public class VpnTest {
|
|||||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore);
|
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
|
||||||
fail("Expected SecurityException due to restricted user");
|
fail("Expected SecurityException due to restricted user");
|
||||||
} catch (SecurityException expected) {
|
} catch (SecurityException expected) {
|
||||||
}
|
}
|
||||||
@@ -694,10 +691,10 @@ public class VpnTest {
|
|||||||
public void testDeleteVpnProfile() throws Exception {
|
public void testDeleteVpnProfile() throws Exception {
|
||||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||||
|
|
||||||
vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
|
vpn.deleteVpnProfile(TEST_VPN_PKG);
|
||||||
|
|
||||||
verify(mKeyStore)
|
verify(mVpnProfileStore)
|
||||||
.delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID));
|
.remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -707,7 +704,7 @@ public class VpnTest {
|
|||||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
|
vpn.deleteVpnProfile(TEST_VPN_PKG);
|
||||||
fail("Expected SecurityException due to restricted user");
|
fail("Expected SecurityException due to restricted user");
|
||||||
} catch (SecurityException expected) {
|
} catch (SecurityException expected) {
|
||||||
}
|
}
|
||||||
@@ -717,24 +714,24 @@ public class VpnTest {
|
|||||||
public void testGetVpnProfilePrivileged() throws Exception {
|
public void testGetVpnProfilePrivileged() throws Exception {
|
||||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||||
|
|
||||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||||
.thenReturn(new VpnProfile("").encode());
|
.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
|
@Test
|
||||||
public void testStartVpnProfile() throws Exception {
|
public void testStartVpnProfile() throws Exception {
|
||||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
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());
|
.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)
|
verify(mAppOps)
|
||||||
.noteOpNoThrow(
|
.noteOpNoThrow(
|
||||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
||||||
@@ -748,10 +745,10 @@ public class VpnTest {
|
|||||||
public void testStartVpnProfileVpnServicePreconsented() throws Exception {
|
public void testStartVpnProfileVpnServicePreconsented() throws Exception {
|
||||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
|
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());
|
.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 that the the ACTIVATE_VPN appop was checked, but no error was thrown.
|
||||||
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
|
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
|
||||||
@@ -763,7 +760,7 @@ public class VpnTest {
|
|||||||
final Vpn vpn = createVpnAndSetupUidChecks();
|
final Vpn vpn = createVpnAndSetupUidChecks();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||||
fail("Expected failure due to no user consent");
|
fail("Expected failure due to no user consent");
|
||||||
} catch (SecurityException expected) {
|
} catch (SecurityException expected) {
|
||||||
}
|
}
|
||||||
@@ -780,22 +777,22 @@ public class VpnTest {
|
|||||||
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
|
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
|
||||||
|
|
||||||
// Keystore should never have been accessed.
|
// Keystore should never have been accessed.
|
||||||
verify(mKeyStore, never()).get(any());
|
verify(mVpnProfileStore, never()).get(any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartVpnProfileMissingProfile() throws Exception {
|
public void testStartVpnProfileMissingProfile() throws Exception {
|
||||||
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
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 {
|
try {
|
||||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||||
fail("Expected failure due to missing profile");
|
fail("Expected failure due to missing profile");
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
|
verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
|
||||||
verify(mAppOps)
|
verify(mAppOps)
|
||||||
.noteOpNoThrow(
|
.noteOpNoThrow(
|
||||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
|
||||||
@@ -812,7 +809,7 @@ public class VpnTest {
|
|||||||
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
restrictedProfileA, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
|
vpn.startVpnProfile(TEST_VPN_PKG);
|
||||||
fail("Expected SecurityException due to restricted user");
|
fail("Expected SecurityException due to restricted user");
|
||||||
} catch (SecurityException expected) {
|
} catch (SecurityException expected) {
|
||||||
}
|
}
|
||||||
@@ -938,9 +935,9 @@ public class VpnTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
|
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(
|
verify(mAppOps).setMode(
|
||||||
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
|
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
|
||||||
eq(AppOpsManager.MODE_ALLOWED));
|
eq(AppOpsManager.MODE_ALLOWED));
|
||||||
@@ -963,11 +960,11 @@ public class VpnTest {
|
|||||||
final int uid = Process.myUid() + 1;
|
final int uid = Process.myUid() + 1;
|
||||||
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
|
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
|
||||||
.thenReturn(uid);
|
.thenReturn(uid);
|
||||||
when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
|
||||||
.thenReturn(mVpnProfile.encode());
|
.thenReturn(mVpnProfile.encode());
|
||||||
|
|
||||||
setAndVerifyAlwaysOnPackage(vpn, uid, false);
|
setAndVerifyAlwaysOnPackage(vpn, uid, false);
|
||||||
assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
|
assertTrue(vpn.startAlwaysOnVpn());
|
||||||
|
|
||||||
// TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
|
// TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
|
||||||
// a subsequent CL.
|
// a subsequent CL.
|
||||||
@@ -984,7 +981,7 @@ public class VpnTest {
|
|||||||
InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
|
InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
|
||||||
lp.addRoute(defaultRoute);
|
lp.addRoute(defaultRoute);
|
||||||
|
|
||||||
vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp);
|
vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
|
||||||
return vpn;
|
return vpn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1186,7 +1183,7 @@ public class VpnTest {
|
|||||||
.thenReturn(asUserContext);
|
.thenReturn(asUserContext);
|
||||||
final TestLooper testLooper = new TestLooper();
|
final TestLooper testLooper = new TestLooper();
|
||||||
final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
|
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(
|
verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
|
||||||
provider -> provider.getName().contains("VpnNetworkProvider")
|
provider -> provider.getName().contains("VpnNetworkProvider")
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user