Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70f90282 authored by Benedict Wong's avatar Benedict Wong Committed by Gerrit Code Review
Browse files

Merge changes from topic "settings-vpn"

* changes:
  Minor cleanup
  Add support for starting IKEv2/IPsec VPNs from settings
  Add always-on VPN support for platform VPNs
parents 2f9800a4 66da080a
Loading
Loading
Loading
Loading
+73 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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 = "";

@@ -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;
@@ -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());
@@ -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;
@@ -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.
     *
@@ -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");

+5 −1
Original line number Diff line number Diff line
@@ -126,7 +126,11 @@ public class VpnManager {
        return getIntentForConfirmation();
    }

    /** Delete the VPN profile configuration that was provisioned by the calling app */
    /**
     * Delete the VPN profile configuration that was provisioned by the calling app
     *
     * @throws SecurityException if this would violate user settings
     */
    public void deleteProvisionedVpnProfile() {
        try {
            mService.deleteVpnProfile(mContext.getOpPackageName());
+29 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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. */
+7 −7
Original line number Diff line number Diff line
@@ -4783,7 +4783,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
                return false;
            }

            return vpn.startAlwaysOnVpn();
            return vpn.startAlwaysOnVpn(mKeyStore);
        }
    }

@@ -4798,7 +4798,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
                Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                return false;
            }
            return vpn.isAlwaysOnPackageSupported(packageName);
            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
        }
    }

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

File changed.

Preview size limit exceeded, changes collapsed.

Loading