summaryrefslogtreecommitdiff
path: root/core/java/android
diff options
context:
space:
mode:
authorBenedict Wong <benedictwong@google.com>2020-01-23 05:11:47 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-01-23 05:11:47 +0000
commitadb79e3b3946ccd5cc91bd4123991dce7d2e6ad7 (patch)
tree7494a669feae87be51c14a40456e06f275db8162 /core/java/android
parentdb46c349d80895abd36a0c3ec55b752762cbfe12 (diff)
parent50b444359e95d5325d4c7da536893c45e529d28b (diff)
Merge changes from topic "add-ikev2-vpn-types"
* changes: Add VpnManger API surface Add Ikev2VpnProfile as public API Add additional fields to VpnProfile for profile-based IKEv2/IPsec VPNs
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/SystemServiceRegistry.java10
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/net/Ikev2VpnProfile.java728
-rw-r--r--core/java/android/net/LinkProperties.java3
-rw-r--r--core/java/android/net/PlatformVpnProfile.java107
-rw-r--r--core/java/android/net/VpnManager.java88
6 files changed, 944 insertions, 1 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8e12a221b5f7..81f6d28db9fe 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -113,6 +113,7 @@ import android.net.NetworkScoreManager;
import android.net.NetworkWatchlistManager;
import android.net.TestNetworkManager;
import android.net.TetheringManager;
+import android.net.VpnManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -370,6 +371,15 @@ final class SystemServiceRegistry {
return new IpSecManager(ctx, service);
}});
+ registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
+ new CachedServiceFetcher<VpnManager>() {
+ @Override
+ public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+ return new VpnManager(ctx, service);
+ }});
+
registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
ConnectivityDiagnosticsManager.class,
new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 725fd0531911..472d9567e2ad 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3296,6 +3296,7 @@ public abstract class Context {
CONNECTIVITY_SERVICE,
//@hide: IP_MEMORY_STORE_SERVICE,
IPSEC_SERVICE,
+ VPN_MANAGEMENT_SERVICE,
TEST_NETWORK_SERVICE,
//@hide: UPDATE_LOCK_SERVICE,
//@hide: NETWORKMANAGEMENT_SERVICE,
@@ -3880,6 +3881,14 @@ public abstract class Context {
public static final String IPSEC_SERVICE = "ipsec";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.VpnManager} to
+ * manage profiles for the platform built-in VPN.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics
* as well as receiving network connectivity information from the system.
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
new file mode 100644
index 000000000000..42b4da14d879
--- /dev/null
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2019 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.net;
+
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK;
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA;
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.security.Credentials;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.net.VpnProfile;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The Ikev2VpnProfile is a configuration for the platform setup of IKEv2/IPsec VPNs.
+ *
+ * <p>Together with VpnManager, this allows apps to provision IKEv2/IPsec VPNs that do not require
+ * the VPN app to constantly run in the background.
+ *
+ * @see VpnManager
+ * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296 - Internet Key
+ * Exchange, Version 2 (IKEv2)</a>
+ */
+public final class Ikev2VpnProfile extends PlatformVpnProfile {
+ private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
+ private static final String EMPTY_CERT = "";
+
+ @NonNull private final String mServerAddr;
+ @NonNull private final String mUserIdentity;
+
+ // PSK authentication
+ @Nullable private final byte[] mPresharedKey;
+
+ // Username/Password, RSA authentication
+ @Nullable private final X509Certificate mServerRootCaCert;
+
+ // Username/Password authentication
+ @Nullable private final String mUsername;
+ @Nullable private final String mPassword;
+
+ // RSA Certificate authentication
+ @Nullable private final PrivateKey mRsaPrivateKey;
+ @Nullable private final X509Certificate mUserCert;
+
+ @Nullable private final ProxyInfo mProxyInfo;
+ @NonNull private final List<String> mAllowedAlgorithms;
+ private final boolean mIsBypassable; // Defaults in builder
+ private final boolean mIsMetered; // Defaults in builder
+ private final int mMaxMtu; // Defaults in builder
+
+ private Ikev2VpnProfile(
+ int type,
+ @NonNull String serverAddr,
+ @NonNull String userIdentity,
+ @Nullable byte[] presharedKey,
+ @Nullable X509Certificate serverRootCaCert,
+ @Nullable String username,
+ @Nullable String password,
+ @Nullable PrivateKey rsaPrivateKey,
+ @Nullable X509Certificate userCert,
+ @Nullable ProxyInfo proxyInfo,
+ @NonNull List<String> allowedAlgorithms,
+ boolean isBypassable,
+ boolean isMetered,
+ int maxMtu) {
+ super(type);
+
+ checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address");
+ checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
+ checkNotNull(allowedAlgorithms, MISSING_PARAM_MSG_TMPL, "Allowed Algorithms");
+
+ mServerAddr = serverAddr;
+ mUserIdentity = userIdentity;
+ mPresharedKey =
+ presharedKey == null ? null : Arrays.copyOf(presharedKey, presharedKey.length);
+ mServerRootCaCert = serverRootCaCert;
+ mUsername = username;
+ mPassword = password;
+ mRsaPrivateKey = rsaPrivateKey;
+ mUserCert = userCert;
+ mProxyInfo = new ProxyInfo(proxyInfo);
+
+ // UnmodifiableList doesn't make a defensive copy by default.
+ mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms));
+
+ mIsBypassable = isBypassable;
+ mIsMetered = isMetered;
+ mMaxMtu = maxMtu;
+
+ validate();
+ }
+
+ private void validate() {
+ // Server Address not validated except to check an address was provided. This allows for
+ // dual-stack servers and hostname based addresses.
+ checkStringNotEmpty(mServerAddr, MISSING_PARAM_MSG_TMPL, "Server Address");
+ checkStringNotEmpty(mUserIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
+
+ // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6
+ // networks, the VPN must provide a link fulfilling the stricter of the two conditions
+ // (at least that of the IPv6 MTU).
+ if (mMaxMtu < LinkProperties.MIN_MTU_V6) {
+ throw new IllegalArgumentException(
+ "Max MTU must be at least" + LinkProperties.MIN_MTU_V6);
+ }
+
+ switch (mType) {
+ case TYPE_IKEV2_IPSEC_USER_PASS:
+ checkNotNull(mUsername, MISSING_PARAM_MSG_TMPL, "Username");
+ checkNotNull(mPassword, MISSING_PARAM_MSG_TMPL, "Password");
+
+ if (mServerRootCaCert != null) checkCert(mServerRootCaCert);
+
+ break;
+ case TYPE_IKEV2_IPSEC_PSK:
+ checkNotNull(mPresharedKey, MISSING_PARAM_MSG_TMPL, "Preshared Key");
+ break;
+ case TYPE_IKEV2_IPSEC_RSA:
+ checkNotNull(mUserCert, MISSING_PARAM_MSG_TMPL, "User cert");
+ checkNotNull(mRsaPrivateKey, MISSING_PARAM_MSG_TMPL, "RSA Private key");
+
+ checkCert(mUserCert);
+ if (mServerRootCaCert != null) checkCert(mServerRootCaCert);
+
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid auth method set");
+ }
+
+ VpnProfile.validateAllowedAlgorithms(mAllowedAlgorithms);
+ }
+
+ /** Retrieves the server address string. */
+ @NonNull
+ public String getServerAddr() {
+ return mServerAddr;
+ }
+
+ /** Retrieves the user identity. */
+ @NonNull
+ public String getUserIdentity() {
+ return mUserIdentity;
+ }
+
+ /**
+ * Retrieves the pre-shared key.
+ *
+ * <p>May be null if the profile is not using Pre-shared key authentication.
+ */
+ @Nullable
+ public byte[] getPresharedKey() {
+ return mPresharedKey == null ? null : Arrays.copyOf(mPresharedKey, mPresharedKey.length);
+ }
+
+ /**
+ * Retrieves the certificate for the server's root CA.
+ *
+ * <p>May be null if the profile is not using RSA Digital Signature Authentication or
+ * Username/Password authentication
+ */
+ @Nullable
+ public X509Certificate getServerRootCaCert() {
+ return mServerRootCaCert;
+ }
+
+ /**
+ * Retrieves the username.
+ *
+ * <p>May be null if the profile is not using Username/Password authentication
+ */
+ @Nullable
+ public String getUsername() {
+ return mUsername;
+ }
+
+ /**
+ * Retrieves the password.
+ *
+ * <p>May be null if the profile is not using Username/Password authentication
+ */
+ @Nullable
+ public String getPassword() {
+ return mPassword;
+ }
+
+ /**
+ * Retrieves the RSA private key.
+ *
+ * <p>May be null if the profile is not using RSA Digital Signature authentication
+ */
+ @Nullable
+ public PrivateKey getRsaPrivateKey() {
+ return mRsaPrivateKey;
+ }
+
+ /** Retrieves the user certificate, if any was set. */
+ @Nullable
+ public X509Certificate getUserCert() {
+ return mUserCert;
+ }
+
+ /** Retrieves the proxy information if any was set */
+ @Nullable
+ public ProxyInfo getProxyInfo() {
+ return mProxyInfo;
+ }
+
+ /** Returns all the algorithms allowed by this VPN profile. */
+ @NonNull
+ public List<String> getAllowedAlgorithms() {
+ return mAllowedAlgorithms;
+ }
+
+ /** Returns whether or not the VPN profile should be bypassable. */
+ public boolean isBypassable() {
+ return mIsBypassable;
+ }
+
+ /** Returns whether or not the VPN profile should be always considered metered. */
+ public boolean isMetered() {
+ return mIsMetered;
+ }
+
+ /** Retrieves the maximum MTU set for this VPN profile. */
+ public int getMaxMtu() {
+ return mMaxMtu;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mType,
+ mServerAddr,
+ mUserIdentity,
+ Arrays.hashCode(mPresharedKey),
+ mServerRootCaCert,
+ mUsername,
+ mPassword,
+ mRsaPrivateKey,
+ mUserCert,
+ mProxyInfo,
+ mAllowedAlgorithms,
+ mIsBypassable,
+ mIsMetered,
+ mMaxMtu);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Ikev2VpnProfile)) {
+ return false;
+ }
+
+ final Ikev2VpnProfile other = (Ikev2VpnProfile) obj;
+ return mType == other.mType
+ && Objects.equals(mServerAddr, other.mServerAddr)
+ && Objects.equals(mUserIdentity, other.mUserIdentity)
+ && Arrays.equals(mPresharedKey, other.mPresharedKey)
+ && Objects.equals(mServerRootCaCert, other.mServerRootCaCert)
+ && Objects.equals(mUsername, other.mUsername)
+ && Objects.equals(mPassword, other.mPassword)
+ && Objects.equals(mRsaPrivateKey, other.mRsaPrivateKey)
+ && Objects.equals(mUserCert, other.mUserCert)
+ && Objects.equals(mProxyInfo, other.mProxyInfo)
+ && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
+ && mIsBypassable == other.mIsBypassable
+ && mIsMetered == other.mIsMetered
+ && mMaxMtu == other.mMaxMtu;
+ }
+
+ /**
+ * Builds a VpnProfile instance for internal use, based on the stored IKEv2/IPsec parameters.
+ *
+ * <p>Redundant authentication information (from previous calls to other setAuth* methods) will
+ * be discarded.
+ *
+ * @hide
+ */
+ @NonNull
+ public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
+ final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */);
+ profile.type = mType;
+ profile.server = mServerAddr;
+ profile.ipsecIdentifier = mUserIdentity;
+ profile.proxy = mProxyInfo;
+ profile.setAllowedAlgorithms(mAllowedAlgorithms);
+ profile.isBypassable = mIsBypassable;
+ profile.isMetered = mIsMetered;
+ profile.maxMtu = mMaxMtu;
+ profile.areAuthParamsInline = true;
+ profile.saveLogin = true;
+
+ switch (mType) {
+ case TYPE_IKEV2_IPSEC_USER_PASS:
+ profile.username = mUsername;
+ profile.password = mPassword;
+ profile.ipsecCaCert =
+ mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
+ break;
+ case TYPE_IKEV2_IPSEC_PSK:
+ profile.ipsecSecret = encodeForIpsecSecret(mPresharedKey);
+ break;
+ case TYPE_IKEV2_IPSEC_RSA:
+ profile.ipsecUserCert = certificateToPemString(mUserCert);
+ profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
+ profile.ipsecCaCert =
+ mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid auth method set");
+ }
+
+ return profile;
+ }
+
+ /**
+ * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance.
+ *
+ * <p>Redundant authentication information (not related to profile type) will be discarded.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
+ throws IOException, GeneralSecurityException {
+ final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
+ builder.setProxy(profile.proxy);
+ builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
+ builder.setBypassable(profile.isBypassable);
+ builder.setMetered(profile.isMetered);
+ builder.setMaxMtu(profile.maxMtu);
+
+ switch (profile.type) {
+ case TYPE_IKEV2_IPSEC_USER_PASS:
+ builder.setAuthUsernamePassword(
+ profile.username,
+ profile.password,
+ certificateFromPemString(profile.ipsecCaCert));
+ break;
+ case TYPE_IKEV2_IPSEC_PSK:
+ builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret));
+ break;
+ case TYPE_IKEV2_IPSEC_RSA:
+ final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert);
+ final PrivateKey key = getPrivateKey(profile.ipsecSecret);
+ final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert);
+ builder.setAuthDigitalSignature(userCert, key, serverRootCa);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid auth method set");
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Converts a X509 Certificate to a PEM-formatted string.
+ *
+ * <p>Must be public due to runtime-package restrictions.
+ *
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static String certificateToPemString(@Nullable X509Certificate cert)
+ throws IOException, CertificateEncodingException {
+ if (cert == null) {
+ return EMPTY_CERT;
+ }
+
+ // Credentials.convertToPem outputs ASCII bytes.
+ return new String(Credentials.convertToPem(cert), StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Decodes the provided Certificate(s).
+ *
+ * <p>Will use the first one if the certStr encodes more than one certificate.
+ */
+ @Nullable
+ private static X509Certificate certificateFromPemString(@Nullable String certStr)
+ throws CertificateException {
+ if (certStr == null || EMPTY_CERT.equals(certStr)) {
+ return null;
+ }
+
+ try {
+ final List<X509Certificate> certs =
+ Credentials.convertFromPem(certStr.getBytes(StandardCharsets.US_ASCII));
+ return certs.isEmpty() ? null : certs.get(0);
+ } catch (IOException e) {
+ throw new CertificateException(e);
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static String encodeForIpsecSecret(@NonNull byte[] secret) {
+ checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret");
+
+ return Base64.getEncoder().encodeToString(secret);
+ }
+
+ @NonNull
+ private static byte[] decodeFromIpsecSecret(@NonNull String encoded) {
+ checkNotNull(encoded, MISSING_PARAM_MSG_TMPL, "encoded");
+
+ return Base64.getDecoder().decode(encoded);
+ }
+
+ @NonNull
+ private static PrivateKey getPrivateKey(@NonNull String keyStr)
+ throws InvalidKeySpecException, NoSuchAlgorithmException {
+ final PKCS8EncodedKeySpec privateKeySpec =
+ new PKCS8EncodedKeySpec(decodeFromIpsecSecret(keyStr));
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePrivate(privateKeySpec);
+ }
+
+ private static void checkCert(@NonNull X509Certificate cert) {
+ try {
+ certificateToPemString(cert);
+ } catch (GeneralSecurityException | IOException e) {
+ throw new IllegalArgumentException("Certificate could not be encoded");
+ }
+ }
+
+ private static @NonNull <T> T checkNotNull(
+ final T reference, final String messageTemplate, final Object... messageArgs) {
+ return Objects.requireNonNull(reference, String.format(messageTemplate, messageArgs));
+ }
+
+ /** A incremental builder for IKEv2 VPN profiles */
+ public static final class Builder {
+ private int mType = -1;
+ @NonNull private final String mServerAddr;
+ @NonNull private final String mUserIdentity;
+
+ // PSK authentication
+ @Nullable private byte[] mPresharedKey;
+
+ // Username/Password, RSA authentication
+ @Nullable private X509Certificate mServerRootCaCert;
+
+ // Username/Password authentication
+ @Nullable private String mUsername;
+ @Nullable private String mPassword;
+
+ // RSA Certificate authentication
+ @Nullable private PrivateKey mRsaPrivateKey;
+ @Nullable private X509Certificate mUserCert;
+
+ @Nullable private ProxyInfo mProxyInfo;
+ @NonNull private List<String> mAllowedAlgorithms = new ArrayList<>();
+ private boolean mIsBypassable = false;
+ private boolean mIsMetered = true;
+ private int mMaxMtu = 1360;
+
+ /**
+ * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN.
+ *
+ * @param serverAddr the server that the VPN should connect to
+ * @param identity the identity string to be used for IKEv2 authentication
+ */
+ public Builder(@NonNull String serverAddr, @NonNull String identity) {
+ checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "serverAddr");
+ checkNotNull(identity, MISSING_PARAM_MSG_TMPL, "identity");
+
+ mServerAddr = serverAddr;
+ mUserIdentity = identity;
+ }
+
+ private void resetAuthParams() {
+ mPresharedKey = null;
+ mServerRootCaCert = null;
+ mUsername = null;
+ mPassword = null;
+ mRsaPrivateKey = null;
+ mUserCert = null;
+ }
+
+ /**
+ * Set the IKEv2 authentication to use the provided username/password.
+ *
+ * <p>Setting this will configure IKEv2 authentication using EAP-MSCHAPv2. Only one
+ * authentication method may be set. This method will overwrite any previously set
+ * authentication method.
+ *
+ * @param user the username to be used for EAP-MSCHAPv2 authentication
+ * @param pass the password to be used for EAP-MSCHAPv2 authentication
+ * @param serverRootCa the root certificate to be used for verifying the identity of the
+ * server
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ * @throws IllegalArgumentException if any of the certificates were invalid or of an
+ * unrecognized format
+ */
+ @NonNull
+ public Builder setAuthUsernamePassword(
+ @NonNull String user,
+ @NonNull String pass,
+ @Nullable X509Certificate serverRootCa) {
+ checkNotNull(user, MISSING_PARAM_MSG_TMPL, "user");
+ checkNotNull(pass, MISSING_PARAM_MSG_TMPL, "pass");
+
+ // Test to make sure all auth params can be encoded safely.
+ if (serverRootCa != null) checkCert(serverRootCa);
+
+ resetAuthParams();
+ mUsername = user;
+ mPassword = pass;
+ mServerRootCaCert = serverRootCa;
+ mType = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ return this;
+ }
+
+ /**
+ * Set the IKEv2 authentication to use Digital Signature Authentication with the given key.
+ *
+ * <p>Setting this will configure IKEv2 authentication using a Digital Signature scheme.
+ * Only one authentication method may be set. This method will overwrite any previously set
+ * authentication method.
+ *
+ * @param userCert the username to be used for RSA Digital signiture authentication
+ * @param key the PrivateKey instance associated with the user ceritificate, used for
+ * constructing the signature
+ * @param serverRootCa the root certificate to be used for verifying the identity of the
+ * server
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ * @throws IllegalArgumentException if any of the certificates were invalid or of an
+ * unrecognized format
+ */
+ @NonNull
+ public Builder setAuthDigitalSignature(
+ @NonNull X509Certificate userCert,
+ @NonNull PrivateKey key,
+ @Nullable X509Certificate serverRootCa) {
+ checkNotNull(userCert, MISSING_PARAM_MSG_TMPL, "userCert");
+ checkNotNull(key, MISSING_PARAM_MSG_TMPL, "key");
+
+ // Test to make sure all auth params can be encoded safely.
+ checkCert(userCert);
+ if (serverRootCa != null) checkCert(serverRootCa);
+
+ resetAuthParams();
+ mUserCert = userCert;
+ mRsaPrivateKey = key;
+ mServerRootCaCert = serverRootCa;
+ mType = VpnProfile.TYPE_IKEV2_IPSEC_RSA;
+ return this;
+ }
+
+ /**
+ * Set the IKEv2 authentication to use Preshared keys.
+ *
+ * <p>Setting this will configure IKEv2 authentication using a Preshared Key. Only one
+ * authentication method may be set. This method will overwrite any previously set
+ * authentication method.
+ *
+ * @param psk the key to be used for Pre-Shared Key authentication
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ */
+ @NonNull
+ public Builder setAuthPsk(@NonNull byte[] psk) {
+ checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk");
+
+ resetAuthParams();
+ mPresharedKey = psk;
+ mType = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
+ return this;
+ }
+
+ /**
+ * Sets whether apps can bypass this VPN connection.
+ *
+ * <p>By default, all traffic from apps are forwarded through the VPN interface and it is
+ * not possible for unprivileged apps to side-step the VPN. If a VPN is set to bypassable,
+ * apps may use methods such as {@link Network#getSocketFactory} or {@link
+ * Network#openConnection} to instead send/receive directly over the underlying network or
+ * any other network they have permissions for.
+ *
+ * @param isBypassable Whether or not the VPN should be considered bypassable. Defaults to
+ * {@code false}.
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ */
+ @NonNull
+ public Builder setBypassable(boolean isBypassable) {
+ mIsBypassable = isBypassable;
+ return this;
+ }
+
+ /**
+ * Sets a proxy for the VPN network.
+ *
+ * <p>Note that this proxy is only a recommendation and it may be ignored by apps.
+ *
+ * @param proxy the ProxyInfo to be set for the VPN network
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ */
+ @NonNull
+ public Builder setProxy(@Nullable ProxyInfo proxy) {
+ mProxyInfo = proxy;
+ return this;
+ }
+
+ /**
+ * Set the upper bound of the maximum transmission unit (MTU) of the VPN interface.
+ *
+ * <p>If it is not set, a safe value will be used. Additionally, the actual link MTU will be
+ * dynamically calculated/updated based on the underlying link's mtu.
+ *
+ * @param mtu the MTU (in bytes) of the VPN interface
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ * @throws IllegalArgumentException if the value is not at least the minimum IPv6 MTU (1280)
+ */
+ @NonNull
+ public Builder setMaxMtu(int mtu) {
+ // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6
+ // networks, the VPN must provide a link fulfilling the stricter of the two conditions
+ // (at least that of the IPv6 MTU).
+ if (mtu < LinkProperties.MIN_MTU_V6) {
+ throw new IllegalArgumentException(
+ "Max MTU must be at least " + LinkProperties.MIN_MTU_V6);
+ }
+ mMaxMtu = mtu;
+ return this;
+ }
+
+ /**
+ * Marks the VPN network as metered.
+ *
+ * <p>A VPN network is classified as metered when the user is sensitive to heavy data usage
+ * due to monetary costs and/or data limitations. In such cases, you should set this to
+ * {@code true} so that apps on the system can avoid doing large data transfers. Otherwise,
+ * set this to {@code false}. Doing so would cause VPN network to inherit its meteredness
+ * from the underlying network.
+ *
+ * @param isMetered {@code true} if the VPN network should be treated as metered regardless
+ * of underlying network meteredness. Defaults to {@code true}.
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ * @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+ */
+ @NonNull
+ public Builder setMetered(boolean isMetered) {
+ mIsMetered = isMetered;
+ return this;
+ }
+
+ /**
+ * Sets the allowable set of IPsec algorithms
+ *
+ * <p>A list of allowed IPsec algorithms as defined in {@link IpSecAlgorithm}
+ *
+ * @param algorithmNames the list of supported IPsec algorithms
+ * @return this {@link Builder} object to facilitate chaining of method calls
+ * @see IpSecAlgorithm
+ */
+ @NonNull
+ public Builder setAllowedAlgorithms(@NonNull List<String> algorithmNames) {
+ checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames");
+ VpnProfile.validateAllowedAlgorithms(algorithmNames);
+
+ mAllowedAlgorithms = algorithmNames;
+ return this;
+ }
+
+ /**
+ * Validates, builds and provisions the VpnProfile.
+ *
+ * @throws IllegalArgumentException if any of the required keys or values were invalid
+ */
+ @NonNull
+ public Ikev2VpnProfile build() {
+ return new Ikev2VpnProfile(
+ mType,
+ mServerAddr,
+ mUserIdentity,
+ mPresharedKey,
+ mServerRootCaCert,
+ mUsername,
+ mPassword,
+ mRsaPrivateKey,
+ mUserCert,
+ mProxyInfo,
+ mAllowedAlgorithms,
+ mIsBypassable,
+ mIsMetered,
+ mMaxMtu);
+ }
+ }
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index ec773ef4a1e2..d25ee0e69e88 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -80,7 +80,8 @@ public final class LinkProperties implements Parcelable {
private final transient boolean mParcelSensitiveFields;
private static final int MIN_MTU = 68;
- private static final int MIN_MTU_V6 = 1280;
+ /* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */
+ static final int MIN_MTU_V6 = 1280;
private static final int MAX_MTU = 10000;
private static final int INET6_ADDR_LENGTH = 16;
diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java
new file mode 100644
index 000000000000..fbae63707be2
--- /dev/null
+++ b/core/java/android/net/PlatformVpnProfile.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.net;
+
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK;
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA;
+import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.internal.net.VpnProfile;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.GeneralSecurityException;
+
+/**
+ * PlatformVpnProfile represents a configuration for a platform-based VPN implementation.
+ *
+ * <p>Platform-based VPNs allow VPN applications to provide configuration and authentication options
+ * to leverage the Android OS' implementations of well-defined control plane (authentication, key
+ * negotiation) and data plane (per-packet encryption) protocols to simplify the creation of VPN
+ * tunnels. In contrast, {@link VpnService} based VPNs must implement both the control and data
+ * planes on a per-app basis.
+ *
+ * @see Ikev2VpnProfile
+ */
+public abstract class PlatformVpnProfile {
+ /**
+ * Alias to platform VPN related types from VpnProfile, for API use.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ TYPE_IKEV2_IPSEC_USER_PASS,
+ TYPE_IKEV2_IPSEC_PSK,
+ TYPE_IKEV2_IPSEC_RSA,
+ })
+ public static @interface PlatformVpnType {}
+
+ public static final int TYPE_IKEV2_IPSEC_USER_PASS = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ public static final int TYPE_IKEV2_IPSEC_PSK = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
+ public static final int TYPE_IKEV2_IPSEC_RSA = VpnProfile.TYPE_IKEV2_IPSEC_RSA;
+
+ /** @hide */
+ @PlatformVpnType protected final int mType;
+
+ /** @hide */
+ PlatformVpnProfile(@PlatformVpnType int type) {
+ mType = type;
+ }
+ /** Returns the profile integer type. */
+ @PlatformVpnType
+ public final int getType() {
+ return mType;
+ }
+
+ /** Returns a type string describing the VPN profile type */
+ @NonNull
+ public final String getTypeString() {
+ switch (mType) {
+ case TYPE_IKEV2_IPSEC_USER_PASS:
+ return "IKEv2/IPsec Username/Password";
+ case TYPE_IKEV2_IPSEC_PSK:
+ return "IKEv2/IPsec Preshared key";
+ case TYPE_IKEV2_IPSEC_RSA:
+ return "IKEv2/IPsec RSA Digital Signature";
+ default:
+ return "Unknown VPN profile type";
+ }
+ }
+
+ /** @hide */
+ @NonNull
+ public abstract VpnProfile toVpnProfile() throws IOException, GeneralSecurityException;
+
+ /** @hide */
+ @NonNull
+ public static PlatformVpnProfile fromVpnProfile(@NonNull VpnProfile profile)
+ throws IOException, GeneralSecurityException {
+ switch (profile.type) {
+ case TYPE_IKEV2_IPSEC_USER_PASS: // fallthrough
+ case TYPE_IKEV2_IPSEC_PSK: // fallthrough
+ case TYPE_IKEV2_IPSEC_RSA:
+ return Ikev2VpnProfile.fromVpnProfile(profile);
+ default:
+ throw new IllegalArgumentException("Unknown VPN Profile type");
+ }
+ }
+}
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
new file mode 100644
index 000000000000..f95807a14f00
--- /dev/null
+++ b/core/java/android/net/VpnManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 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.net;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * This class provides an interface for apps to manage platform VPN profiles
+ *
+ * <p>Apps can use this API to provide profiles with which the platform can set up a VPN without
+ * further app intermediation. When a VPN profile is present and the app is selected as an always-on
+ * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the
+ * app (unlike VpnService).
+ *
+ * <p>VPN apps using supported protocols should preferentially use this API over the {@link
+ * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user
+ * the guarantee that VPN network traffic is not subjected to on-device packet interception.
+ *
+ * @see Ikev2VpnProfile
+ */
+public class VpnManager {
+ @NonNull private final Context mContext;
+ @NonNull private final IConnectivityManager mService;
+
+ /**
+ * Create an instance of the VpnManger with the given context.
+ *
+ * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the
+ * {@link Context.getSystemService()} method call.
+ *
+ * @hide
+ */
+ public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
+ mContext = checkNotNull(ctx, "missing Context");
+ mService = checkNotNull(service, "missing IConnectivityManager");
+ }
+
+ /**
+ * Install a VpnProfile configuration keyed on the calling app's package name.
+ *
+ * @param profile the PlatformVpnProfile provided by this package. Will override any previous
+ * PlatformVpnProfile stored for this package.
+ * @return an intent to request user consent if needed (null otherwise).
+ */
+ @Nullable
+ public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ /** Delete the VPN profile configuration that was provisioned by the calling app */
+ public void deleteProvisionedVpnProfile() {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ /**
+ * Request the startup of a previously provisioned VPN.
+ *
+ * @throws SecurityException exception if user or device settings prevent this VPN from being
+ * setup, or if user consent has not been granted
+ */
+ public void startProvisionedVpnProfile() {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ /** Tear down the VPN provided by the calling app (if any) */
+ public void stopProvisionedVpnProfile() {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}