Loading Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -526,6 +526,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", "devicepolicyprotosnano", "devicepolicyprotosnano", Loading core/java/android/net/Ikev2VpnProfile.java +19 −23 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 = ""; Loading Loading @@ -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()); Loading @@ -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 { Loading keystore/java/android/security/LegacyVpnProfileStore.java 0 → 100644 +142 −0 Original line number Original line Diff line number Diff line /* * 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]; } } services/core/java/com/android/server/VpnManagerService.java +23 −22 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading @@ -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(); Loading Loading @@ -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); } } } } Loading @@ -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); } } } } Loading @@ -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); } } } } Loading Loading @@ -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); } } } } Loading Loading @@ -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 Loading @@ -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); Loading @@ -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; Loading Loading @@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; return false; } } return vpn.startAlwaysOnVpn(mKeyStore); return vpn.startAlwaysOnVpn(); } } } } Loading @@ -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); } } } } Loading @@ -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; } } } } Loading Loading @@ -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(); Loading Loading @@ -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(); } } } } } } Loading @@ -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); } } } } } } Loading Loading @@ -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 { Loading services/core/java/com/android/server/connectivity/Vpn.java +142 −73 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
Android.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -526,6 +526,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", "devicepolicyprotosnano", "devicepolicyprotosnano", Loading
core/java/android/net/Ikev2VpnProfile.java +19 −23 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 = ""; Loading Loading @@ -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()); Loading @@ -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 { Loading
keystore/java/android/security/LegacyVpnProfileStore.java 0 → 100644 +142 −0 Original line number Original line Diff line number Diff line /* * 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]; } }
services/core/java/com/android/server/VpnManagerService.java +23 −22 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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() { Loading @@ -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(); Loading Loading @@ -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); } } } } Loading @@ -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); } } } } Loading @@ -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); } } } } Loading Loading @@ -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); } } } } Loading Loading @@ -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 Loading @@ -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); Loading @@ -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; Loading Loading @@ -495,7 +495,7 @@ public class VpnManagerService extends IVpnManager.Stub { return false; return false; } } return vpn.startAlwaysOnVpn(mKeyStore); return vpn.startAlwaysOnVpn(); } } } } Loading @@ -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); } } } } Loading @@ -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; } } } } Loading Loading @@ -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(); Loading Loading @@ -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(); } } } } } } Loading @@ -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); } } } } } } Loading Loading @@ -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 { Loading
services/core/java/com/android/server/connectivity/Vpn.java +142 −73 File changed.Preview size limit exceeded, changes collapsed. Show changes