1210 lines
50 KiB
Java
1210 lines
50 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|
|
}
|