Loading core/java/android/net/Ikev2VpnProfile.java +73 −3 Original line number Diff line number Diff line Loading @@ -25,7 +25,10 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Process; import android.security.Credentials; import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.VpnProfile; Loading Loading @@ -59,6 +62,11 @@ import java.util.Objects; * Exchange, Version 2 (IKEv2)</a> */ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */ public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:"; /** Prefix for when a Private Key is stored directly in the profile @hide */ public static final String PREFIX_INLINE = "INLINE:"; private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; private static final String EMPTY_CERT = ""; Loading Loading @@ -339,7 +347,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { break; case TYPE_IKEV2_IPSEC_RSA: profile.ipsecUserCert = certificateToPemString(mUserCert); profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); profile.ipsecSecret = PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); profile.ipsecCaCert = mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); break; Loading @@ -360,6 +369,22 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @NonNull public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) throws IOException, GeneralSecurityException { return fromVpnProfile(profile, null); } /** * Builds the Ikev2VpnProfile from the given profile. * * @param profile the source VpnProfile to build from * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if * the private key is PEM-encoded into the profile. * @return The IKEv2/IPsec VPN profile * @hide */ @NonNull public static Ikev2VpnProfile fromVpnProfile( @NonNull VpnProfile profile, @Nullable KeyStore keyStore) throws IOException, GeneralSecurityException { final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); builder.setProxy(profile.proxy); builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); Loading @@ -378,8 +403,21 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret)); break; case TYPE_IKEV2_IPSEC_RSA: final PrivateKey key; if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey"); final String alias = profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( keyStore, alias, Process.myUid()); } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) { key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length())); } else { throw new IllegalArgumentException("Invalid RSA private key prefix"); } final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert); final PrivateKey key = getPrivateKey(profile.ipsecSecret); final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert); builder.setAuthDigitalSignature(userCert, key, serverRootCa); break; Loading @@ -390,6 +428,39 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return builder.build(); } /** * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile. * * @hide */ public static boolean isValidVpnProfile(@NonNull VpnProfile profile) { if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) { return false; } switch (profile.type) { case TYPE_IKEV2_IPSEC_USER_PASS: if (profile.username.isEmpty() || profile.password.isEmpty()) { return false; } break; case TYPE_IKEV2_IPSEC_PSK: if (profile.ipsecSecret.isEmpty()) { return false; } break; case TYPE_IKEV2_IPSEC_RSA: if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) { return false; } break; default: return false; } return true; } /** * Converts a X509 Certificate to a PEM-formatted string. * Loading Loading @@ -432,7 +503,6 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PRIVATE) public static String encodeForIpsecSecret(@NonNull byte[] secret) { checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret"); Loading core/java/com/android/internal/net/VpnProfile.java +29 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.internal.net; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.net.Ikev2VpnProfile; import android.net.ProxyInfo; import android.os.Build; import android.os.Parcel; Loading Loading @@ -332,15 +333,38 @@ public final class VpnProfile implements Cloneable, Parcelable { return builder.toString().getBytes(StandardCharsets.UTF_8); } /** Checks if this profile specifies a LegacyVpn type. */ public static boolean isLegacyType(int type) { switch (type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through case VpnProfile.TYPE_IKEV2_IPSEC_PSK: return false; default: return true; } } private boolean isValidLockdownLegacyVpnProfile() { return isLegacyType(type) && isServerAddressNumeric() && hasDns() && areDnsAddressesNumeric(); } private boolean isValidLockdownPlatformVpnProfile() { return Ikev2VpnProfile.isValidVpnProfile(this); } /** * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS. * Server hostnames would require using DNS before connection. * Tests if profile is valid for lockdown. * * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS. * * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to * be non-null. */ public boolean isValidLockdownProfile() { return isTypeValidForLockdown() && isServerAddressNumeric() && hasDns() && areDnsAddressesNumeric(); && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile()); } /** Returns {@code true} if the VPN type is valid for lockdown. */ Loading services/core/java/com/android/server/connectivity/Vpn.java +48 −13 Original line number Diff line number Diff line Loading @@ -690,13 +690,14 @@ public class Vpn { // Prefer VPN profiles, if any exist. VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore); 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 // correctly parsed, and the VPN has started running in a different thread. The only // other possibility is that the above call threw an exception, which will be // caught below, and returns false (clearing the always-on VPN). Once started, the // Platform VPN cannot permanantly fail, and is resiliant to temporary failures. It // Platform VPN cannot permanently fail, and is resilient to temporary failures. It // will continue retrying until shut down by the user, or always-on is toggled off. return true; } Loading Loading @@ -818,6 +819,7 @@ public class Vpn { } /** Prepare the VPN for the given package. Does not perform permission checks. */ @GuardedBy("this") private void prepareInternal(String newPackage) { long token = Binder.clearCallingIdentity(); try { Loading Loading @@ -1943,6 +1945,27 @@ public class Vpn { // Prepare arguments for racoon. String[] racoon = null; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // Secret key is still just the alias (not the actual private key). The private key // is retrieved from the KeyStore during conversion of the VpnProfile to an // Ikev2VpnProfile. profile.ipsecSecret = Ikev2VpnProfile.PREFIX_KEYSTORE_ALIAS + privateKey; profile.ipsecUserCert = userCert; // Fallthrough case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: profile.ipsecCaCert = caCert; // Start VPN profile startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // Ikev2VpnProfiles expect a base64-encoded preshared key. profile.ipsecSecret = Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes()); // Start VPN profile startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_L2TP_IPSEC_PSK: racoon = new String[] { iface, profile.server, "udppsk", profile.ipsecIdentifier, Loading Loading @@ -2949,24 +2972,35 @@ public class Vpn { throw new IllegalArgumentException("No profile found for " + packageName); } startVpnProfilePrivileged(profile, packageName); startVpnProfilePrivileged(profile, packageName, null /* keyStore for private key retrieval - unneeded */); }); } private void startVpnProfilePrivileged( @NonNull VpnProfile profile, @NonNull String packageName) { // Ensure that no other previous instance is running. if (mVpnRunner != null) { mVpnRunner.exit(); mVpnRunner = null; } private synchronized void startVpnProfilePrivileged( @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) { // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(), // by the Setting app via startLegacyVpn(), or by ConnectivityService via // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the // nice property of ensuring there are no other VpnRunner instances running. prepareInternal(packageName); updateState(DetailedState.CONNECTING, "startPlatformVpn"); try { // Build basic config mConfig = new VpnConfig(); if (VpnConfig.LEGACY_VPN.equals(packageName)) { mConfig.legacy = true; mConfig.session = profile.name; mConfig.user = profile.key; // TODO: Add support for configuring meteredness via Settings. Until then, use a // safe default. mConfig.isMetered = true; } else { mConfig.user = packageName; mConfig.isMetered = profile.isMetered; } mConfig.startTime = SystemClock.elapsedRealtime(); mConfig.proxyInfo = profile.proxy; Loading @@ -2974,7 +3008,8 @@ public class Vpn { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: case VpnProfile.TYPE_IKEV2_IPSEC_PSK: case VpnProfile.TYPE_IKEV2_IPSEC_RSA: mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile)); mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore)); mVpnRunner.start(); break; default: Loading tests/net/java/android/net/Ikev2VpnProfileTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import android.test.mock.MockContext; Loading Loading @@ -232,10 +231,12 @@ public class Ikev2VpnProfileTest { builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); final VpnProfile profile = builder.build().toVpnProfile(); final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()); verifyVpnProfileCommon(profile); assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); assertEquals( Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()), expectedSecret, profile.ipsecSecret); assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); Loading tests/net/java/com/android/server/connectivity/VpnTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -59,9 +59,15 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Ikev2VpnProfile; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnManager; import android.net.VpnService; Loading @@ -84,6 +90,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.server.IpSecService; import org.junit.Before; import org.junit.Test; Loading @@ -93,6 +100,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; Loading Loading @@ -125,6 +133,9 @@ public class VpnTest { } static final String TEST_VPN_PKG = "com.dummy.vpn"; private static final String TEST_VPN_SERVER = "1.2.3.4"; private static final String TEST_VPN_IDENTITY = "identity"; private static final byte[] TEST_VPN_PSK = "psk".getBytes(); /** * Names and UIDs for some fake packages. Important points: Loading @@ -151,23 +162,39 @@ public class VpnTest { @Mock private Vpn.SystemServices mSystemServices; @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; @Mock private KeyStore mKeyStore; private final VpnProfile mVpnProfile = new VpnProfile("key"); private final VpnProfile mVpnProfile; private IpSecManager mIpSecManager; public VpnTest() throws Exception { // Build an actual VPN profile that is capable of being converted to and from an // Ikev2VpnProfile final Ikev2VpnProfile.Builder builder = new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY); builder.setAuthPsk(TEST_VPN_PSK); mVpnProfile = builder.build().toVpnProfile(); } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mIpSecManager = new IpSecManager(mContext, mIpSecService); when(mContext.getPackageManager()).thenReturn(mPackageManager); setMockedPackages(mPackages); when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName()); when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))) .thenReturn(mConnectivityManager); when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager); when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) .thenReturn(Resources.getSystem().getString( R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); Loading Loading @@ -962,6 +989,26 @@ public class VpnTest { // a subsequent CL. } @Test public void testStartLegacyVpn() throws Exception { final Vpn vpn = createVpn(primaryUser.id); setMockedUsers(primaryUser); // Dummy egress interface final String egressIface = "DUMMY0"; final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(egressIface); final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), InetAddresses.parseNumericAddress("192.0.2.0"), egressIface); lp.addRoute(defaultRoute); vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp); // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in // a subsequent CL. } /** * Mock some methods of vpn object. */ Loading Loading
core/java/android/net/Ikev2VpnProfile.java +73 −3 Original line number Diff line number Diff line Loading @@ -25,7 +25,10 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Process; import android.security.Credentials; import android.security.KeyStore; import android.security.keystore.AndroidKeyStoreProvider; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.VpnProfile; Loading Loading @@ -59,6 +62,11 @@ import java.util.Objects; * Exchange, Version 2 (IKEv2)</a> */ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */ public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:"; /** Prefix for when a Private Key is stored directly in the profile @hide */ public static final String PREFIX_INLINE = "INLINE:"; private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; private static final String EMPTY_CERT = ""; Loading Loading @@ -339,7 +347,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { break; case TYPE_IKEV2_IPSEC_RSA: profile.ipsecUserCert = certificateToPemString(mUserCert); profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); profile.ipsecSecret = PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); profile.ipsecCaCert = mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); break; Loading @@ -360,6 +369,22 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @NonNull public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) throws IOException, GeneralSecurityException { return fromVpnProfile(profile, null); } /** * Builds the Ikev2VpnProfile from the given profile. * * @param profile the source VpnProfile to build from * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if * the private key is PEM-encoded into the profile. * @return The IKEv2/IPsec VPN profile * @hide */ @NonNull public static Ikev2VpnProfile fromVpnProfile( @NonNull VpnProfile profile, @Nullable KeyStore keyStore) throws IOException, GeneralSecurityException { final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); builder.setProxy(profile.proxy); builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); Loading @@ -378,8 +403,21 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret)); break; case TYPE_IKEV2_IPSEC_RSA: final PrivateKey key; if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey"); final String alias = profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( keyStore, alias, Process.myUid()); } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) { key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length())); } else { throw new IllegalArgumentException("Invalid RSA private key prefix"); } final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert); final PrivateKey key = getPrivateKey(profile.ipsecSecret); final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert); builder.setAuthDigitalSignature(userCert, key, serverRootCa); break; Loading @@ -390,6 +428,39 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { return builder.build(); } /** * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile. * * @hide */ public static boolean isValidVpnProfile(@NonNull VpnProfile profile) { if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) { return false; } switch (profile.type) { case TYPE_IKEV2_IPSEC_USER_PASS: if (profile.username.isEmpty() || profile.password.isEmpty()) { return false; } break; case TYPE_IKEV2_IPSEC_PSK: if (profile.ipsecSecret.isEmpty()) { return false; } break; case TYPE_IKEV2_IPSEC_RSA: if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) { return false; } break; default: return false; } return true; } /** * Converts a X509 Certificate to a PEM-formatted string. * Loading Loading @@ -432,7 +503,6 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** @hide */ @NonNull @VisibleForTesting(visibility = Visibility.PRIVATE) public static String encodeForIpsecSecret(@NonNull byte[] secret) { checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret"); Loading
core/java/com/android/internal/net/VpnProfile.java +29 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.internal.net; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.net.Ikev2VpnProfile; import android.net.ProxyInfo; import android.os.Build; import android.os.Parcel; Loading Loading @@ -332,15 +333,38 @@ public final class VpnProfile implements Cloneable, Parcelable { return builder.toString().getBytes(StandardCharsets.UTF_8); } /** Checks if this profile specifies a LegacyVpn type. */ public static boolean isLegacyType(int type) { switch (type) { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through case VpnProfile.TYPE_IKEV2_IPSEC_PSK: return false; default: return true; } } private boolean isValidLockdownLegacyVpnProfile() { return isLegacyType(type) && isServerAddressNumeric() && hasDns() && areDnsAddressesNumeric(); } private boolean isValidLockdownPlatformVpnProfile() { return Ikev2VpnProfile.isValidVpnProfile(this); } /** * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS. * Server hostnames would require using DNS before connection. * Tests if profile is valid for lockdown. * * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS. * * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to * be non-null. */ public boolean isValidLockdownProfile() { return isTypeValidForLockdown() && isServerAddressNumeric() && hasDns() && areDnsAddressesNumeric(); && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile()); } /** Returns {@code true} if the VPN type is valid for lockdown. */ Loading
services/core/java/com/android/server/connectivity/Vpn.java +48 −13 Original line number Diff line number Diff line Loading @@ -690,13 +690,14 @@ public class Vpn { // Prefer VPN profiles, if any exist. VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore); 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 // correctly parsed, and the VPN has started running in a different thread. The only // other possibility is that the above call threw an exception, which will be // caught below, and returns false (clearing the always-on VPN). Once started, the // Platform VPN cannot permanantly fail, and is resiliant to temporary failures. It // Platform VPN cannot permanently fail, and is resilient to temporary failures. It // will continue retrying until shut down by the user, or always-on is toggled off. return true; } Loading Loading @@ -818,6 +819,7 @@ public class Vpn { } /** Prepare the VPN for the given package. Does not perform permission checks. */ @GuardedBy("this") private void prepareInternal(String newPackage) { long token = Binder.clearCallingIdentity(); try { Loading Loading @@ -1943,6 +1945,27 @@ public class Vpn { // Prepare arguments for racoon. String[] racoon = null; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // Secret key is still just the alias (not the actual private key). The private key // is retrieved from the KeyStore during conversion of the VpnProfile to an // Ikev2VpnProfile. profile.ipsecSecret = Ikev2VpnProfile.PREFIX_KEYSTORE_ALIAS + privateKey; profile.ipsecUserCert = userCert; // Fallthrough case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: profile.ipsecCaCert = caCert; // Start VPN profile startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // Ikev2VpnProfiles expect a base64-encoded preshared key. profile.ipsecSecret = Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes()); // Start VPN profile startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore); return; case VpnProfile.TYPE_L2TP_IPSEC_PSK: racoon = new String[] { iface, profile.server, "udppsk", profile.ipsecIdentifier, Loading Loading @@ -2949,24 +2972,35 @@ public class Vpn { throw new IllegalArgumentException("No profile found for " + packageName); } startVpnProfilePrivileged(profile, packageName); startVpnProfilePrivileged(profile, packageName, null /* keyStore for private key retrieval - unneeded */); }); } private void startVpnProfilePrivileged( @NonNull VpnProfile profile, @NonNull String packageName) { // Ensure that no other previous instance is running. if (mVpnRunner != null) { mVpnRunner.exit(); mVpnRunner = null; } private synchronized void startVpnProfilePrivileged( @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) { // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(), // by the Setting app via startLegacyVpn(), or by ConnectivityService via // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the // nice property of ensuring there are no other VpnRunner instances running. prepareInternal(packageName); updateState(DetailedState.CONNECTING, "startPlatformVpn"); try { // Build basic config mConfig = new VpnConfig(); if (VpnConfig.LEGACY_VPN.equals(packageName)) { mConfig.legacy = true; mConfig.session = profile.name; mConfig.user = profile.key; // TODO: Add support for configuring meteredness via Settings. Until then, use a // safe default. mConfig.isMetered = true; } else { mConfig.user = packageName; mConfig.isMetered = profile.isMetered; } mConfig.startTime = SystemClock.elapsedRealtime(); mConfig.proxyInfo = profile.proxy; Loading @@ -2974,7 +3008,8 @@ public class Vpn { case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: case VpnProfile.TYPE_IKEV2_IPSEC_PSK: case VpnProfile.TYPE_IKEV2_IPSEC_RSA: mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile)); mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore)); mVpnRunner.start(); break; default: Loading
tests/net/java/android/net/Ikev2VpnProfileTest.java +3 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import android.test.mock.MockContext; Loading Loading @@ -232,10 +231,12 @@ public class Ikev2VpnProfileTest { builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); final VpnProfile profile = builder.build().toVpnProfile(); final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()); verifyVpnProfileCommon(profile); assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); assertEquals( Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()), expectedSecret, profile.ipsecSecret); assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); Loading
tests/net/java/com/android/server/connectivity/VpnTest.java +49 −2 Original line number Diff line number Diff line Loading @@ -59,9 +59,15 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Ikev2VpnProfile; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; import android.net.RouteInfo; import android.net.UidRange; import android.net.VpnManager; import android.net.VpnService; Loading @@ -84,6 +90,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; import com.android.server.IpSecService; import org.junit.Before; import org.junit.Test; Loading @@ -93,6 +100,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; Loading Loading @@ -125,6 +133,9 @@ public class VpnTest { } static final String TEST_VPN_PKG = "com.dummy.vpn"; private static final String TEST_VPN_SERVER = "1.2.3.4"; private static final String TEST_VPN_IDENTITY = "identity"; private static final byte[] TEST_VPN_PSK = "psk".getBytes(); /** * Names and UIDs for some fake packages. Important points: Loading @@ -151,23 +162,39 @@ public class VpnTest { @Mock private Vpn.SystemServices mSystemServices; @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; @Mock private KeyStore mKeyStore; private final VpnProfile mVpnProfile = new VpnProfile("key"); private final VpnProfile mVpnProfile; private IpSecManager mIpSecManager; public VpnTest() throws Exception { // Build an actual VPN profile that is capable of being converted to and from an // Ikev2VpnProfile final Ikev2VpnProfile.Builder builder = new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY); builder.setAuthPsk(TEST_VPN_PSK); mVpnProfile = builder.build().toVpnProfile(); } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mIpSecManager = new IpSecManager(mContext, mIpSecService); when(mContext.getPackageManager()).thenReturn(mPackageManager); setMockedPackages(mPackages); when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName()); when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))) .thenReturn(mConnectivityManager); when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager); when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) .thenReturn(Resources.getSystem().getString( R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); Loading Loading @@ -962,6 +989,26 @@ public class VpnTest { // a subsequent CL. } @Test public void testStartLegacyVpn() throws Exception { final Vpn vpn = createVpn(primaryUser.id); setMockedUsers(primaryUser); // Dummy egress interface final String egressIface = "DUMMY0"; final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(egressIface); final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), InetAddresses.parseNumericAddress("192.0.2.0"), egressIface); lp.addRoute(defaultRoute); vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp); // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in // a subsequent CL. } /** * Mock some methods of vpn object. */ Loading