| /* |
| * 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.IpSecAlgorithm.AUTH_AES_CMAC; |
| import static android.net.IpSecAlgorithm.AUTH_AES_XCBC; |
| import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM; |
| import static android.net.IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305; |
| import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA256; |
| import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA384; |
| import static android.net.IpSecAlgorithm.AUTH_HMAC_SHA512; |
| import static android.net.IpSecAlgorithm.CRYPT_AES_CBC; |
| import static android.net.IpSecAlgorithm.CRYPT_AES_CTR; |
| |
| import static com.android.internal.annotations.VisibleForTesting.Visibility; |
| import static com.android.internal.util.Preconditions.checkStringNotEmpty; |
| import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresFeature; |
| import android.content.pm.PackageManager; |
| import android.net.ipsec.ike.IkeDerAsn1DnIdentification; |
| import android.net.ipsec.ike.IkeFqdnIdentification; |
| import android.net.ipsec.ike.IkeIdentification; |
| import android.net.ipsec.ike.IkeIpv4AddrIdentification; |
| import android.net.ipsec.ike.IkeIpv6AddrIdentification; |
| import android.net.ipsec.ike.IkeKeyIdIdentification; |
| import android.net.ipsec.ike.IkeRfc822AddrIdentification; |
| import android.net.ipsec.ike.IkeSessionParams; |
| import android.net.ipsec.ike.IkeTunnelConnectionParams; |
| import android.security.Credentials; |
| import android.util.Log; |
| |
| 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.Key; |
| import java.security.KeyFactory; |
| import java.security.KeyStore; |
| 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 TAG = Ikev2VpnProfile.class.getSimpleName(); |
| /** 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 ANDROID_KEYSTORE_PROVIDER = "AndroidKeyStore"; |
| private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; |
| private static final String EMPTY_CERT = ""; |
| |
| /** @hide */ |
| public static final List<String> DEFAULT_ALGORITHMS; |
| |
| private static void addAlgorithmIfSupported(List<String> algorithms, String ipSecAlgoName) { |
| if (IpSecAlgorithm.getSupportedAlgorithms().contains(ipSecAlgoName)) { |
| algorithms.add(ipSecAlgoName); |
| } |
| } |
| |
| static { |
| final List<String> algorithms = new ArrayList<>(); |
| addAlgorithmIfSupported(algorithms, CRYPT_AES_CBC); |
| addAlgorithmIfSupported(algorithms, CRYPT_AES_CTR); |
| addAlgorithmIfSupported(algorithms, AUTH_HMAC_SHA256); |
| addAlgorithmIfSupported(algorithms, AUTH_HMAC_SHA384); |
| addAlgorithmIfSupported(algorithms, AUTH_HMAC_SHA512); |
| addAlgorithmIfSupported(algorithms, AUTH_AES_XCBC); |
| addAlgorithmIfSupported(algorithms, AUTH_AES_CMAC); |
| addAlgorithmIfSupported(algorithms, AUTH_CRYPT_AES_GCM); |
| addAlgorithmIfSupported(algorithms, AUTH_CRYPT_CHACHA20_POLY1305); |
| |
| DEFAULT_ALGORITHMS = Collections.unmodifiableList(algorithms); |
| } |
| |
| @Nullable private final String mServerAddr; |
| @Nullable 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 final boolean mIsRestrictedToTestNetworks; |
| @Nullable private final IkeTunnelConnectionParams mIkeTunConnParams; |
| private final boolean mAutomaticNattKeepaliveTimerEnabled; |
| private final boolean mAutomaticIpVersionSelectionEnabled; |
| |
| private Ikev2VpnProfile( |
| int type, |
| @Nullable String serverAddr, |
| @Nullable 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, |
| boolean restrictToTestNetworks, |
| boolean excludeLocalRoutes, |
| boolean requiresInternetValidation, |
| @Nullable IkeTunnelConnectionParams ikeTunConnParams, |
| boolean automaticNattKeepaliveTimerEnabled, |
| boolean automaticIpVersionSelectionEnabled) { |
| super(type, excludeLocalRoutes, requiresInternetValidation); |
| |
| 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 = (proxyInfo == null) ? null : new ProxyInfo(proxyInfo); |
| |
| // UnmodifiableList doesn't make a defensive copy by default. |
| mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms)); |
| if (excludeLocalRoutes && !isBypassable) { |
| throw new IllegalArgumentException( |
| "Vpn must be bypassable if excludeLocalRoutes is set"); |
| } |
| |
| mIsBypassable = isBypassable; |
| mIsMetered = isMetered; |
| mMaxMtu = maxMtu; |
| mIsRestrictedToTestNetworks = restrictToTestNetworks; |
| mIkeTunConnParams = ikeTunConnParams; |
| mAutomaticNattKeepaliveTimerEnabled = automaticNattKeepaliveTimerEnabled; |
| mAutomaticIpVersionSelectionEnabled = automaticIpVersionSelectionEnabled; |
| |
| validate(); |
| } |
| |
| private void validate() { |
| // 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 < IPV6_MIN_MTU) { |
| throw new IllegalArgumentException("Max MTU must be at least" + IPV6_MIN_MTU); |
| } |
| |
| // Skip validating the other fields if mIkeTunConnParams is set because the required |
| // information should all come from the mIkeTunConnParams. |
| if (mIkeTunConnParams != null) return; |
| |
| // 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"); |
| |
| 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"); |
| } |
| |
| validateAllowedAlgorithms(mAllowedAlgorithms); |
| } |
| |
| /** |
| * Validates that the allowed algorithms are a valid set for IPsec purposes |
| * |
| * <p>In order for the algorithm list to be a valid set, it must contain at least one algorithm |
| * that provides Authentication, and one that provides Encryption. Authenticated Encryption with |
| * Associated Data (AEAD) algorithms are counted as providing Authentication and Encryption. |
| * |
| * @param algorithmNames The list to be validated |
| */ |
| private static void validateAllowedAlgorithms(@NonNull List<String> algorithmNames) { |
| // First, make sure no insecure algorithms were proposed. |
| if (algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_MD5) |
| || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA1)) { |
| throw new IllegalArgumentException("Algorithm not supported for IKEv2 VPN profiles"); |
| } |
| |
| // Validate that some valid combination (AEAD or AUTH + CRYPT) is present |
| if (hasAeadAlgorithms(algorithmNames) || hasNormalModeAlgorithms(algorithmNames)) { |
| return; |
| } |
| |
| throw new IllegalArgumentException("Algorithm set missing support for Auth, Crypt or both"); |
| } |
| |
| /** |
| * Checks if the provided list has AEAD algorithms |
| * |
| * @hide |
| */ |
| public static boolean hasAeadAlgorithms(@NonNull List<String> algorithmNames) { |
| return algorithmNames.contains(IpSecAlgorithm.AUTH_CRYPT_AES_GCM); |
| } |
| |
| /** |
| * Checks the provided list has acceptable (non-AEAD) authentication and encryption algorithms |
| * |
| * @hide |
| */ |
| public static boolean hasNormalModeAlgorithms(@NonNull List<String> algorithmNames) { |
| final boolean hasCrypt = algorithmNames.contains(IpSecAlgorithm.CRYPT_AES_CBC); |
| final boolean hasAuth = algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA256) |
| || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA384) |
| || algorithmNames.contains(IpSecAlgorithm.AUTH_HMAC_SHA512); |
| |
| return hasCrypt && hasAuth; |
| } |
| |
| /** Retrieves the server address string. */ |
| @NonNull |
| public String getServerAddr() { |
| if (mIkeTunConnParams == null) return mServerAddr; |
| |
| final IkeSessionParams ikeSessionParams = mIkeTunConnParams.getIkeSessionParams(); |
| return ikeSessionParams.getServerHostname(); |
| } |
| |
| /** Retrieves the user identity. */ |
| @NonNull |
| public String getUserIdentity() { |
| if (mIkeTunConnParams == null) return mUserIdentity; |
| |
| final IkeSessionParams ikeSessionParams = mIkeTunConnParams.getIkeSessionParams(); |
| return getUserIdentityFromIkeSession(ikeSessionParams); |
| } |
| |
| /** |
| * Retrieves the pre-shared key. |
| * |
| * <p>May be null if the profile is not using Pre-shared key authentication, or the profile is |
| * built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public byte[] getPresharedKey() { |
| if (mIkeTunConnParams != null) return null; |
| |
| 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, or the profile is built from an |
| * {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public X509Certificate getServerRootCaCert() { |
| if (mIkeTunConnParams != null) return null; |
| |
| return mServerRootCaCert; |
| } |
| /** |
| * Retrieves the username. |
| * |
| * <p>May be null if the profile is not using Username/Password authentication, or the profile |
| * is built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public String getUsername() { |
| if (mIkeTunConnParams != null) return null; |
| |
| return mUsername; |
| } |
| |
| /** |
| * Retrieves the password. |
| * |
| * <p>May be null if the profile is not using Username/Password authentication, or the profile |
| * is built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public String getPassword() { |
| if (mIkeTunConnParams != null) return null; |
| |
| return mPassword; |
| } |
| |
| /** |
| * Retrieves the RSA private key. |
| * |
| * <p>May be null if the profile is not using RSA Digital Signature authentication, or the |
| * profile is built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public PrivateKey getRsaPrivateKey() { |
| if (mIkeTunConnParams != null) return null; |
| |
| return mRsaPrivateKey; |
| } |
| |
| /** Retrieves the user certificate, if any was set. |
| * |
| * <p>May be null if the profile is built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @Nullable |
| public X509Certificate getUserCert() { |
| if (mIkeTunConnParams != null) return null; |
| |
| 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. |
| * |
| * <p>May be an empty list if the profile is built from an {@link IkeTunnelConnectionParams}. |
| */ |
| @NonNull |
| public List<String> getAllowedAlgorithms() { |
| if (mIkeTunConnParams != null) return new ArrayList<>(); |
| |
| 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; |
| } |
| |
| /** Retrieves the ikeTunnelConnectionParams contains IKEv2 configurations, if any was set. */ |
| @Nullable |
| public IkeTunnelConnectionParams getIkeTunnelConnectionParams() { |
| return mIkeTunConnParams; |
| } |
| |
| /** |
| * Returns whether or not this VPN profile is restricted to test networks. |
| * |
| * @hide |
| */ |
| public boolean isRestrictedToTestNetworks() { |
| return mIsRestrictedToTestNetworks; |
| } |
| |
| /** Returns whether automatic NAT-T keepalive timers are enabled. */ |
| public boolean isAutomaticNattKeepaliveTimerEnabled() { |
| return mAutomaticNattKeepaliveTimerEnabled; |
| } |
| |
| /** Returns whether automatic IP version selection is enabled. */ |
| public boolean isAutomaticIpVersionSelectionEnabled() { |
| return mAutomaticIpVersionSelectionEnabled; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash( |
| mType, |
| mServerAddr, |
| mUserIdentity, |
| Arrays.hashCode(mPresharedKey), |
| mServerRootCaCert, |
| mUsername, |
| mPassword, |
| mRsaPrivateKey, |
| mUserCert, |
| mProxyInfo, |
| mAllowedAlgorithms, |
| mIsBypassable, |
| mIsMetered, |
| mMaxMtu, |
| mIsRestrictedToTestNetworks, |
| mExcludeLocalRoutes, |
| mRequiresInternetValidation, |
| mIkeTunConnParams, |
| mAutomaticNattKeepaliveTimerEnabled, |
| mAutomaticIpVersionSelectionEnabled); |
| } |
| |
| @Override |
| public boolean equals(@Nullable 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 |
| && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks |
| && mExcludeLocalRoutes == other.mExcludeLocalRoutes |
| && mRequiresInternetValidation == other.mRequiresInternetValidation |
| && Objects.equals(mIkeTunConnParams, other.mIkeTunConnParams) |
| && mAutomaticNattKeepaliveTimerEnabled == other.mAutomaticNattKeepaliveTimerEnabled |
| && mAutomaticIpVersionSelectionEnabled == other.mAutomaticIpVersionSelectionEnabled; |
| } |
| |
| /** |
| * 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) */, |
| mIsRestrictedToTestNetworks, mExcludeLocalRoutes, mRequiresInternetValidation, |
| mIkeTunConnParams, mAutomaticNattKeepaliveTimerEnabled, |
| mAutomaticIpVersionSelectionEnabled); |
| profile.proxy = mProxyInfo; |
| profile.isBypassable = mIsBypassable; |
| profile.isMetered = mIsMetered; |
| profile.maxMtu = mMaxMtu; |
| profile.areAuthParamsInline = true; |
| profile.saveLogin = true; |
| // The other fields should come from mIkeTunConnParams if it's available. |
| if (mIkeTunConnParams != null) { |
| profile.type = VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS; |
| return profile; |
| } |
| |
| profile.type = mType; |
| profile.server = getServerAddr(); |
| profile.ipsecIdentifier = getUserIdentity(); |
| profile.setAllowedAlgorithms(mAllowedAlgorithms); |
| 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 = |
| PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); |
| profile.ipsecCaCert = |
| mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid auth method set"); |
| } |
| |
| return profile; |
| } |
| |
| private static PrivateKey getPrivateKeyFromAndroidKeystore(String alias) { |
| try { |
| final KeyStore keystore = KeyStore.getInstance(ANDROID_KEYSTORE_PROVIDER); |
| keystore.load(null); |
| final Key key = keystore.getKey(alias, null); |
| if (!(key instanceof PrivateKey)) { |
| throw new IllegalStateException( |
| "Unexpected key type returned from android keystore."); |
| } |
| return (PrivateKey) key; |
| } catch (Exception e) { |
| throw new IllegalStateException("Failed to load key from android keystore.", e); |
| } |
| } |
| |
| /** |
| * Builds the Ikev2VpnProfile from the given profile. |
| * |
| * @param profile the source VpnProfile to build from |
| * @return The IKEv2/IPsec VPN profile |
| * @hide |
| */ |
| @NonNull |
| public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) |
| throws GeneralSecurityException { |
| final Builder builder; |
| if (profile.ikeTunConnParams == null) { |
| builder = new Builder(profile.server, profile.ipsecIdentifier); |
| builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); |
| |
| 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 PrivateKey key; |
| if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) { |
| final String alias = |
| profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length()); |
| key = getPrivateKeyFromAndroidKeystore(alias); |
| } 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 X509Certificate serverRootCa = |
| certificateFromPemString(profile.ipsecCaCert); |
| builder.setAuthDigitalSignature(userCert, key, serverRootCa); |
| break; |
| default: |
| throw new IllegalArgumentException("Invalid auth method set"); |
| } |
| } else { |
| builder = new Builder(profile.ikeTunConnParams); |
| } |
| |
| builder.setProxy(profile.proxy); |
| builder.setBypassable(profile.isBypassable); |
| builder.setMetered(profile.isMetered); |
| builder.setMaxMtu(profile.maxMtu); |
| if (profile.isRestrictedToTestNetworks) { |
| builder.restrictToTestNetworks(); |
| } |
| |
| if (profile.excludeLocalRoutes && !profile.isBypassable) { |
| Log.w(TAG, "ExcludeLocalRoutes should only be set in the bypassable VPN"); |
| } |
| |
| builder.setLocalRoutesExcluded(profile.excludeLocalRoutes && profile.isBypassable); |
| builder.setRequiresInternetValidation(profile.requiresInternetValidation); |
| |
| builder.setAutomaticNattKeepaliveTimerEnabled(profile.automaticNattKeepaliveTimerEnabled); |
| builder.setAutomaticIpVersionSelectionEnabled(profile.automaticIpVersionSelectionEnabled); |
| |
| 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. |
| * |
| * <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 |
| 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)); |
| } |
| |
| private static void checkBuilderSetter(boolean constructedFromIkeTunConParams, |
| @NonNull String field) { |
| if (constructedFromIkeTunConParams) { |
| throw new IllegalArgumentException( |
| field + " can't be set with IkeTunnelConnectionParams builder"); |
| } |
| } |
| |
| @NonNull |
| private static String getUserIdentityFromIkeSession(@NonNull IkeSessionParams params) { |
| final IkeIdentification ident = params.getLocalIdentification(); |
| // Refer to VpnIkev2Utils.parseIkeIdentification(). |
| if (ident instanceof IkeKeyIdIdentification) { |
| return "@#" + new String(((IkeKeyIdIdentification) ident).keyId); |
| } else if (ident instanceof IkeRfc822AddrIdentification) { |
| return "@@" + ((IkeRfc822AddrIdentification) ident).rfc822Name; |
| } else if (ident instanceof IkeFqdnIdentification) { |
| return "@" + ((IkeFqdnIdentification) ident).fqdn; |
| } else if (ident instanceof IkeIpv4AddrIdentification) { |
| return ((IkeIpv4AddrIdentification) ident).ipv4Address.getHostAddress(); |
| } else if (ident instanceof IkeIpv6AddrIdentification) { |
| return ((IkeIpv6AddrIdentification) ident).ipv6Address.getHostAddress(); |
| } else if (ident instanceof IkeDerAsn1DnIdentification) { |
| throw new IllegalArgumentException("Unspported ASN.1 encoded identities"); |
| } else { |
| throw new IllegalArgumentException("Unknown IkeIdentification to get user identity"); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder("IkeV2VpnProfile ["); |
| sb.append(" MaxMtu=" + mMaxMtu); |
| if (mIsBypassable) sb.append(" Bypassable"); |
| if (mRequiresInternetValidation) sb.append(" RequiresInternetValidation"); |
| if (mIsRestrictedToTestNetworks) sb.append(" RestrictedToTestNetworks"); |
| if (mAutomaticNattKeepaliveTimerEnabled) sb.append(" AutomaticNattKeepaliveTimerEnabled"); |
| if (mAutomaticIpVersionSelectionEnabled) sb.append(" AutomaticIpVersionSelectionEnabled"); |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| /** A incremental builder for IKEv2 VPN profiles */ |
| public static final class Builder { |
| private int mType = -1; |
| @Nullable private final String mServerAddr; |
| @Nullable 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 = DEFAULT_ALGORITHMS; |
| private boolean mRequiresInternetValidation = false; |
| private boolean mIsBypassable = false; |
| private boolean mIsMetered = true; |
| private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; |
| private boolean mIsRestrictedToTestNetworks = false; |
| private boolean mExcludeLocalRoutes = false; |
| private boolean mAutomaticNattKeepaliveTimerEnabled = false; |
| private boolean mAutomaticIpVersionSelectionEnabled = false; |
| @Nullable private final IkeTunnelConnectionParams mIkeTunConnParams; |
| |
| /** |
| * 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 |
| */ |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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; |
| |
| mIkeTunConnParams = null; |
| } |
| |
| /** |
| * Creates a new builder from a {@link IkeTunnelConnectionParams} |
| * |
| * @param ikeTunConnParams the {@link IkeTunnelConnectionParams} contains IKEv2 |
| * configurations |
| */ |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder(@NonNull IkeTunnelConnectionParams ikeTunConnParams) { |
| checkNotNull(ikeTunConnParams, MISSING_PARAM_MSG_TMPL, "ikeTunConnParams"); |
| |
| mIkeTunConnParams = ikeTunConnParams; |
| mServerAddr = null; |
| mUserIdentity = null; |
| } |
| |
| 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. |
| * |
| * <p>If this {@link Builder} is constructed with an {@link IkeTunnelConnectionParams}, |
| * authentication details should be configured there, and calling this method will result |
| * in an exception being thrown. |
| * |
| * @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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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"); |
| checkBuilderSetter(mIkeTunConnParams != null, "authUsernamePassword"); |
| |
| // 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. |
| * |
| * <p>If this {@link Builder} is constructed with an {@link IkeTunnelConnectionParams}, |
| * authentication details should be configured there, and calling this method will result in |
| * an exception being thrown. |
| * |
| * @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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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"); |
| checkBuilderSetter(mIkeTunConnParams != null, "authDigitalSignature"); |
| |
| // 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. |
| * |
| * <p>If this {@link Builder} is constructed with an {@link IkeTunnelConnectionParams}, |
| * authentication details should be configured there, and calling this method will result in |
| * an exception being thrown. |
| * |
| * @param psk the key to be used for Pre-Shared Key authentication |
| * @return this {@link Builder} object to facilitate chaining of method calls |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setAuthPsk(@NonNull byte[] psk) { |
| checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk"); |
| checkBuilderSetter(mIkeTunConnParams != null, "authPsk"); |
| |
| 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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| 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 < IPV6_MIN_MTU) { |
| throw new IllegalArgumentException("Max MTU must be at least " + IPV6_MIN_MTU); |
| } |
| mMaxMtu = mtu; |
| return this; |
| } |
| |
| /** |
| * Request that this VPN undergoes Internet validation. |
| * |
| * If this is true, the platform will perform basic validation checks for Internet |
| * connectivity over this VPN. If and when they succeed, the VPN network capabilities will |
| * reflect this by gaining the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} |
| * capability. |
| * |
| * If this is false, the platform assumes the VPN either is always capable of reaching the |
| * Internet or intends not to. In this case, the VPN network capabilities will |
| * always gain the {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} capability |
| * immediately after it connects, whether it can reach public Internet destinations or not. |
| * |
| * @param requiresInternetValidation {@code true} if the framework should attempt to |
| * validate this VPN for Internet connectivity. Defaults |
| * to {@code false}. |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setRequiresInternetValidation(boolean requiresInternetValidation) { |
| mRequiresInternetValidation = requiresInternetValidation; |
| 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 |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setMetered(boolean isMetered) { |
| mIsMetered = isMetered; |
| return this; |
| } |
| |
| /** |
| * Sets the allowable set of IPsec algorithms |
| * |
| * <p>If set, this will constrain the set of algorithms that the IPsec tunnel will use for |
| * integrity verification and encryption to the provided list. |
| * |
| * <p>The set of allowed IPsec algorithms is defined in {@link IpSecAlgorithm}. Adding of |
| * algorithms that are considered insecure (such as AUTH_HMAC_MD5 and AUTH_HMAC_SHA1) is not |
| * permitted, and will result in an IllegalArgumentException being thrown. |
| * |
| * <p>The provided algorithm list must contain at least one algorithm that provides |
| * Authentication, and one that provides Encryption. Authenticated Encryption with |
| * Associated Data (AEAD) algorithms provide both Authentication and Encryption. |
| * |
| * <p>If this {@link Builder} is constructed with an {@link IkeTunnelConnectionParams}, |
| * authentication details should be configured there, and calling this method will result in |
| * an exception being thrown. |
| * |
| * <p>By default, this profile will use any algorithm defined in {@link IpSecAlgorithm}, |
| * with the exception of those considered insecure (as described above). |
| * |
| * @param algorithmNames the list of supported IPsec algorithms |
| * @return this {@link Builder} object to facilitate chaining of method calls |
| * @see IpSecAlgorithm |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setAllowedAlgorithms(@NonNull List<String> algorithmNames) { |
| checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames"); |
| checkBuilderSetter(mIkeTunConnParams != null, "algorithmNames"); |
| validateAllowedAlgorithms(algorithmNames); |
| |
| mAllowedAlgorithms = algorithmNames; |
| return this; |
| } |
| |
| /** |
| * Restricts this profile to use test networks (only). |
| * |
| * <p>This method is for testing only, and must not be used by apps. Calling |
| * provisionVpnProfile() with a profile where test-network usage is enabled will require the |
| * MANAGE_TEST_NETWORKS permission. |
| * |
| * @hide |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder restrictToTestNetworks() { |
| mIsRestrictedToTestNetworks = true; |
| return this; |
| } |
| |
| /** |
| * Sets the enabled state of the automatic NAT-T keepalive timers |
| * |
| * Note that if this builder was constructed with a {@link IkeTunnelConnectionParams}, |
| * but this is called with {@code true}, the framework will automatically choose the |
| * appropriate keepalive timer and ignore the settings in the session params embedded |
| * in the connection params. |
| * |
| * @param isEnabled {@code true} to enable automatic keepalive timers, based on internal |
| * platform signals. Defaults to {@code false}. |
| * @return this {@link Builder} object to facilitate chaining of method calls |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setAutomaticNattKeepaliveTimerEnabled(boolean isEnabled) { |
| mAutomaticNattKeepaliveTimerEnabled = isEnabled; |
| return this; |
| } |
| |
| /** |
| * Sets the enabled state of the automatic IP version selection |
| * |
| * @param isEnabled {@code true} to enable automatic IP version selection, based on internal |
| * platform signals. Defaults to {@code false}. |
| * @return this {@link Builder} object to facilitate chaining of method calls |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setAutomaticIpVersionSelectionEnabled(boolean isEnabled) { |
| mAutomaticIpVersionSelectionEnabled = isEnabled; |
| return this; |
| } |
| |
| /** |
| * Sets whether the local traffic is exempted from the VPN. |
| * |
| * When this is set, the system will not use the VPN network when an app |
| * tries to send traffic for an IP address that is on a local network. |
| * |
| * Note that there are important security implications. In particular, the |
| * networks that the device connects to typically decides what IP addresses |
| * are part of the local network. This means that for VPNs setting this |
| * flag, it is possible for anybody to set up a public network in such a |
| * way that traffic to arbitrary IP addresses will bypass the VPN, including |
| * traffic to services like DNS. When using this API, please consider the |
| * security implications for your particular case. |
| * |
| * Note that because the local traffic will always bypass the VPN, |
| * it is not possible to set this flag on a non-bypassable VPN. |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Builder setLocalRoutesExcluded(boolean excludeLocalRoutes) { |
| mExcludeLocalRoutes = excludeLocalRoutes; |
| return this; |
| } |
| |
| /** |
| * Validates, builds and provisions the VpnProfile. |
| * |
| * @throws IllegalArgumentException if any of the required keys or values were invalid |
| */ |
| @NonNull |
| @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| public Ikev2VpnProfile build() { |
| return new Ikev2VpnProfile( |
| mType, |
| mServerAddr, |
| mUserIdentity, |
| mPresharedKey, |
| mServerRootCaCert, |
| mUsername, |
| mPassword, |
| mRsaPrivateKey, |
| mUserCert, |
| mProxyInfo, |
| mAllowedAlgorithms, |
| mIsBypassable, |
| mIsMetered, |
| mMaxMtu, |
| mIsRestrictedToTestNetworks, |
| mExcludeLocalRoutes, |
| mRequiresInternetValidation, |
| mIkeTunConnParams, |
| mAutomaticNattKeepaliveTimerEnabled, |
| mAutomaticIpVersionSelectionEnabled); |
| } |
| } |
| } |