Merge changes from topic "add-ikev2-vpn-types"
am: adb79e3b39
Change-Id: I205121bbd8ef8adbe5e7a5ea1c57bbcac0efae0e
This commit is contained in:
@@ -9883,6 +9883,7 @@ package android.content {
|
||||
field public static final String USB_SERVICE = "usb";
|
||||
field public static final String USER_SERVICE = "user";
|
||||
field public static final String VIBRATOR_SERVICE = "vibrator";
|
||||
field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
|
||||
field public static final String WALLPAPER_SERVICE = "wallpaper";
|
||||
field public static final String WIFI_AWARE_SERVICE = "wifiaware";
|
||||
field public static final String WIFI_P2P_SERVICE = "wifip2p";
|
||||
@@ -28866,6 +28867,35 @@ package android.net {
|
||||
field public final int code;
|
||||
}
|
||||
|
||||
public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile {
|
||||
method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms();
|
||||
method public int getMaxMtu();
|
||||
method @Nullable public String getPassword();
|
||||
method @Nullable public byte[] getPresharedKey();
|
||||
method @Nullable public android.net.ProxyInfo getProxyInfo();
|
||||
method @Nullable public java.security.PrivateKey getRsaPrivateKey();
|
||||
method @NonNull public String getServerAddr();
|
||||
method @Nullable public java.security.cert.X509Certificate getServerRootCaCert();
|
||||
method @Nullable public java.security.cert.X509Certificate getUserCert();
|
||||
method @NonNull public String getUserIdentity();
|
||||
method @Nullable public String getUsername();
|
||||
method public boolean isBypassable();
|
||||
method public boolean isMetered();
|
||||
}
|
||||
|
||||
public static final class Ikev2VpnProfile.Builder {
|
||||
ctor public Ikev2VpnProfile.Builder(@NonNull String, @NonNull String);
|
||||
method @NonNull public android.net.Ikev2VpnProfile build();
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setAllowedAlgorithms(@NonNull java.util.List<java.lang.String>);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey, @Nullable java.security.cert.X509Certificate);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean);
|
||||
method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo);
|
||||
}
|
||||
|
||||
public class InetAddresses {
|
||||
method public static boolean isNumericAddress(@NonNull String);
|
||||
method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
|
||||
@@ -29212,6 +29242,14 @@ package android.net {
|
||||
field public String response;
|
||||
}
|
||||
|
||||
public abstract class PlatformVpnProfile {
|
||||
method public final int getType();
|
||||
method @NonNull public final String getTypeString();
|
||||
field public static final int TYPE_IKEV2_IPSEC_PSK = 7; // 0x7
|
||||
field public static final int TYPE_IKEV2_IPSEC_RSA = 8; // 0x8
|
||||
field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6
|
||||
}
|
||||
|
||||
public final class Proxy {
|
||||
ctor public Proxy();
|
||||
method @Deprecated public static String getDefaultHost();
|
||||
@@ -29492,6 +29530,13 @@ package android.net {
|
||||
method public String sanitize(String);
|
||||
}
|
||||
|
||||
public class VpnManager {
|
||||
method public void deleteProvisionedVpnProfile();
|
||||
method @Nullable public android.content.Intent provisionVpnProfile(@NonNull android.net.PlatformVpnProfile);
|
||||
method public void startProvisionedVpnProfile();
|
||||
method public void stopProvisionedVpnProfile();
|
||||
}
|
||||
|
||||
public class VpnService extends android.app.Service {
|
||||
ctor public VpnService();
|
||||
method public final boolean isAlwaysOn();
|
||||
|
||||
@@ -113,6 +113,7 @@ import android.net.NetworkScoreManager;
|
||||
import android.net.NetworkWatchlistManager;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.TetheringManager;
|
||||
import android.net.VpnManager;
|
||||
import android.net.lowpan.ILowpanManager;
|
||||
import android.net.lowpan.LowpanManager;
|
||||
import android.net.nsd.INsdManager;
|
||||
@@ -370,6 +371,15 @@ final class SystemServiceRegistry {
|
||||
return new IpSecManager(ctx, service);
|
||||
}});
|
||||
|
||||
registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
|
||||
new CachedServiceFetcher<VpnManager>() {
|
||||
@Override
|
||||
public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
|
||||
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
|
||||
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
|
||||
return new VpnManager(ctx, service);
|
||||
}});
|
||||
|
||||
registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
|
||||
ConnectivityDiagnosticsManager.class,
|
||||
new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
|
||||
|
||||
@@ -3296,6 +3296,7 @@ public abstract class Context {
|
||||
CONNECTIVITY_SERVICE,
|
||||
//@hide: IP_MEMORY_STORE_SERVICE,
|
||||
IPSEC_SERVICE,
|
||||
VPN_MANAGEMENT_SERVICE,
|
||||
TEST_NETWORK_SERVICE,
|
||||
//@hide: UPDATE_LOCK_SERVICE,
|
||||
//@hide: NETWORKMANAGEMENT_SERVICE,
|
||||
@@ -3879,6 +3880,14 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String IPSEC_SERVICE = "ipsec";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a {@link android.net.VpnManager} to
|
||||
* manage profiles for the platform built-in VPN.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
*/
|
||||
public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to retrieve a {@link
|
||||
* android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics
|
||||
|
||||
728
core/java/android/net/Ikev2VpnProfile.java
Normal file
728
core/java/android/net/Ikev2VpnProfile.java
Normal file
@@ -0,0 +1,728 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK;
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA;
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility;
|
||||
import static com.android.internal.util.Preconditions.checkStringNotEmpty;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.security.Credentials;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.net.VpnProfile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The Ikev2VpnProfile is a configuration for the platform setup of IKEv2/IPsec VPNs.
|
||||
*
|
||||
* <p>Together with VpnManager, this allows apps to provision IKEv2/IPsec VPNs that do not require
|
||||
* the VPN app to constantly run in the background.
|
||||
*
|
||||
* @see VpnManager
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296 - Internet Key
|
||||
* Exchange, Version 2 (IKEv2)</a>
|
||||
*/
|
||||
public final class Ikev2VpnProfile extends PlatformVpnProfile {
|
||||
private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
|
||||
private static final String EMPTY_CERT = "";
|
||||
|
||||
@NonNull private final String mServerAddr;
|
||||
@NonNull private final String mUserIdentity;
|
||||
|
||||
// PSK authentication
|
||||
@Nullable private final byte[] mPresharedKey;
|
||||
|
||||
// Username/Password, RSA authentication
|
||||
@Nullable private final X509Certificate mServerRootCaCert;
|
||||
|
||||
// Username/Password authentication
|
||||
@Nullable private final String mUsername;
|
||||
@Nullable private final String mPassword;
|
||||
|
||||
// RSA Certificate authentication
|
||||
@Nullable private final PrivateKey mRsaPrivateKey;
|
||||
@Nullable private final X509Certificate mUserCert;
|
||||
|
||||
@Nullable private final ProxyInfo mProxyInfo;
|
||||
@NonNull private final List<String> mAllowedAlgorithms;
|
||||
private final boolean mIsBypassable; // Defaults in builder
|
||||
private final boolean mIsMetered; // Defaults in builder
|
||||
private final int mMaxMtu; // Defaults in builder
|
||||
|
||||
private Ikev2VpnProfile(
|
||||
int type,
|
||||
@NonNull String serverAddr,
|
||||
@NonNull String userIdentity,
|
||||
@Nullable byte[] presharedKey,
|
||||
@Nullable X509Certificate serverRootCaCert,
|
||||
@Nullable String username,
|
||||
@Nullable String password,
|
||||
@Nullable PrivateKey rsaPrivateKey,
|
||||
@Nullable X509Certificate userCert,
|
||||
@Nullable ProxyInfo proxyInfo,
|
||||
@NonNull List<String> allowedAlgorithms,
|
||||
boolean isBypassable,
|
||||
boolean isMetered,
|
||||
int maxMtu) {
|
||||
super(type);
|
||||
|
||||
checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address");
|
||||
checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
|
||||
checkNotNull(allowedAlgorithms, MISSING_PARAM_MSG_TMPL, "Allowed Algorithms");
|
||||
|
||||
mServerAddr = serverAddr;
|
||||
mUserIdentity = userIdentity;
|
||||
mPresharedKey =
|
||||
presharedKey == null ? null : Arrays.copyOf(presharedKey, presharedKey.length);
|
||||
mServerRootCaCert = serverRootCaCert;
|
||||
mUsername = username;
|
||||
mPassword = password;
|
||||
mRsaPrivateKey = rsaPrivateKey;
|
||||
mUserCert = userCert;
|
||||
mProxyInfo = new ProxyInfo(proxyInfo);
|
||||
|
||||
// UnmodifiableList doesn't make a defensive copy by default.
|
||||
mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms));
|
||||
|
||||
mIsBypassable = isBypassable;
|
||||
mIsMetered = isMetered;
|
||||
mMaxMtu = maxMtu;
|
||||
|
||||
validate();
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
// Server Address not validated except to check an address was provided. This allows for
|
||||
// dual-stack servers and hostname based addresses.
|
||||
checkStringNotEmpty(mServerAddr, MISSING_PARAM_MSG_TMPL, "Server Address");
|
||||
checkStringNotEmpty(mUserIdentity, MISSING_PARAM_MSG_TMPL, "User Identity");
|
||||
|
||||
// IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6
|
||||
// networks, the VPN must provide a link fulfilling the stricter of the two conditions
|
||||
// (at least that of the IPv6 MTU).
|
||||
if (mMaxMtu < LinkProperties.MIN_MTU_V6) {
|
||||
throw new IllegalArgumentException(
|
||||
"Max MTU must be at least" + LinkProperties.MIN_MTU_V6);
|
||||
}
|
||||
|
||||
switch (mType) {
|
||||
case TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
checkNotNull(mUsername, MISSING_PARAM_MSG_TMPL, "Username");
|
||||
checkNotNull(mPassword, MISSING_PARAM_MSG_TMPL, "Password");
|
||||
|
||||
if (mServerRootCaCert != null) checkCert(mServerRootCaCert);
|
||||
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_PSK:
|
||||
checkNotNull(mPresharedKey, MISSING_PARAM_MSG_TMPL, "Preshared Key");
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
checkNotNull(mUserCert, MISSING_PARAM_MSG_TMPL, "User cert");
|
||||
checkNotNull(mRsaPrivateKey, MISSING_PARAM_MSG_TMPL, "RSA Private key");
|
||||
|
||||
checkCert(mUserCert);
|
||||
if (mServerRootCaCert != null) checkCert(mServerRootCaCert);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid auth method set");
|
||||
}
|
||||
|
||||
VpnProfile.validateAllowedAlgorithms(mAllowedAlgorithms);
|
||||
}
|
||||
|
||||
/** Retrieves the server address string. */
|
||||
@NonNull
|
||||
public String getServerAddr() {
|
||||
return mServerAddr;
|
||||
}
|
||||
|
||||
/** Retrieves the user identity. */
|
||||
@NonNull
|
||||
public String getUserIdentity() {
|
||||
return mUserIdentity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pre-shared key.
|
||||
*
|
||||
* <p>May be null if the profile is not using Pre-shared key authentication.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getPresharedKey() {
|
||||
return mPresharedKey == null ? null : Arrays.copyOf(mPresharedKey, mPresharedKey.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the certificate for the server's root CA.
|
||||
*
|
||||
* <p>May be null if the profile is not using RSA Digital Signature Authentication or
|
||||
* Username/Password authentication
|
||||
*/
|
||||
@Nullable
|
||||
public X509Certificate getServerRootCaCert() {
|
||||
return mServerRootCaCert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the username.
|
||||
*
|
||||
* <p>May be null if the profile is not using Username/Password authentication
|
||||
*/
|
||||
@Nullable
|
||||
public String getUsername() {
|
||||
return mUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the password.
|
||||
*
|
||||
* <p>May be null if the profile is not using Username/Password authentication
|
||||
*/
|
||||
@Nullable
|
||||
public String getPassword() {
|
||||
return mPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the RSA private key.
|
||||
*
|
||||
* <p>May be null if the profile is not using RSA Digital Signature authentication
|
||||
*/
|
||||
@Nullable
|
||||
public PrivateKey getRsaPrivateKey() {
|
||||
return mRsaPrivateKey;
|
||||
}
|
||||
|
||||
/** Retrieves the user certificate, if any was set. */
|
||||
@Nullable
|
||||
public X509Certificate getUserCert() {
|
||||
return mUserCert;
|
||||
}
|
||||
|
||||
/** Retrieves the proxy information if any was set */
|
||||
@Nullable
|
||||
public ProxyInfo getProxyInfo() {
|
||||
return mProxyInfo;
|
||||
}
|
||||
|
||||
/** Returns all the algorithms allowed by this VPN profile. */
|
||||
@NonNull
|
||||
public List<String> getAllowedAlgorithms() {
|
||||
return mAllowedAlgorithms;
|
||||
}
|
||||
|
||||
/** Returns whether or not the VPN profile should be bypassable. */
|
||||
public boolean isBypassable() {
|
||||
return mIsBypassable;
|
||||
}
|
||||
|
||||
/** Returns whether or not the VPN profile should be always considered metered. */
|
||||
public boolean isMetered() {
|
||||
return mIsMetered;
|
||||
}
|
||||
|
||||
/** Retrieves the maximum MTU set for this VPN profile. */
|
||||
public int getMaxMtu() {
|
||||
return mMaxMtu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
mType,
|
||||
mServerAddr,
|
||||
mUserIdentity,
|
||||
Arrays.hashCode(mPresharedKey),
|
||||
mServerRootCaCert,
|
||||
mUsername,
|
||||
mPassword,
|
||||
mRsaPrivateKey,
|
||||
mUserCert,
|
||||
mProxyInfo,
|
||||
mAllowedAlgorithms,
|
||||
mIsBypassable,
|
||||
mIsMetered,
|
||||
mMaxMtu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Ikev2VpnProfile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Ikev2VpnProfile other = (Ikev2VpnProfile) obj;
|
||||
return mType == other.mType
|
||||
&& Objects.equals(mServerAddr, other.mServerAddr)
|
||||
&& Objects.equals(mUserIdentity, other.mUserIdentity)
|
||||
&& Arrays.equals(mPresharedKey, other.mPresharedKey)
|
||||
&& Objects.equals(mServerRootCaCert, other.mServerRootCaCert)
|
||||
&& Objects.equals(mUsername, other.mUsername)
|
||||
&& Objects.equals(mPassword, other.mPassword)
|
||||
&& Objects.equals(mRsaPrivateKey, other.mRsaPrivateKey)
|
||||
&& Objects.equals(mUserCert, other.mUserCert)
|
||||
&& Objects.equals(mProxyInfo, other.mProxyInfo)
|
||||
&& Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
|
||||
&& mIsBypassable == other.mIsBypassable
|
||||
&& mIsMetered == other.mIsMetered
|
||||
&& mMaxMtu == other.mMaxMtu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a VpnProfile instance for internal use, based on the stored IKEv2/IPsec parameters.
|
||||
*
|
||||
* <p>Redundant authentication information (from previous calls to other setAuth* methods) will
|
||||
* be discarded.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
|
||||
final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */);
|
||||
profile.type = mType;
|
||||
profile.server = mServerAddr;
|
||||
profile.ipsecIdentifier = mUserIdentity;
|
||||
profile.proxy = mProxyInfo;
|
||||
profile.setAllowedAlgorithms(mAllowedAlgorithms);
|
||||
profile.isBypassable = mIsBypassable;
|
||||
profile.isMetered = mIsMetered;
|
||||
profile.maxMtu = mMaxMtu;
|
||||
profile.areAuthParamsInline = true;
|
||||
profile.saveLogin = true;
|
||||
|
||||
switch (mType) {
|
||||
case TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
profile.username = mUsername;
|
||||
profile.password = mPassword;
|
||||
profile.ipsecCaCert =
|
||||
mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_PSK:
|
||||
profile.ipsecSecret = encodeForIpsecSecret(mPresharedKey);
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
profile.ipsecUserCert = certificateToPemString(mUserCert);
|
||||
profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
|
||||
profile.ipsecCaCert =
|
||||
mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid auth method set");
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance.
|
||||
*
|
||||
* <p>Redundant authentication information (not related to profile type) will be discarded.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
||||
throws IOException, GeneralSecurityException {
|
||||
final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
|
||||
builder.setProxy(profile.proxy);
|
||||
builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
|
||||
builder.setBypassable(profile.isBypassable);
|
||||
builder.setMetered(profile.isMetered);
|
||||
builder.setMaxMtu(profile.maxMtu);
|
||||
|
||||
switch (profile.type) {
|
||||
case TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
builder.setAuthUsernamePassword(
|
||||
profile.username,
|
||||
profile.password,
|
||||
certificateFromPemString(profile.ipsecCaCert));
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_PSK:
|
||||
builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret));
|
||||
break;
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert);
|
||||
final PrivateKey key = getPrivateKey(profile.ipsecSecret);
|
||||
final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert);
|
||||
builder.setAuthDigitalSignature(userCert, key, serverRootCa);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid auth method set");
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a X509 Certificate to a PEM-formatted string.
|
||||
*
|
||||
* <p>Must be public due to runtime-package restrictions.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
public static String certificateToPemString(@Nullable X509Certificate cert)
|
||||
throws IOException, CertificateEncodingException {
|
||||
if (cert == null) {
|
||||
return EMPTY_CERT;
|
||||
}
|
||||
|
||||
// Credentials.convertToPem outputs ASCII bytes.
|
||||
return new String(Credentials.convertToPem(cert), StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the provided Certificate(s).
|
||||
*
|
||||
* <p>Will use the first one if the certStr encodes more than one certificate.
|
||||
*/
|
||||
@Nullable
|
||||
private static X509Certificate certificateFromPemString(@Nullable String certStr)
|
||||
throws CertificateException {
|
||||
if (certStr == null || EMPTY_CERT.equals(certStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final List<X509Certificate> certs =
|
||||
Credentials.convertFromPem(certStr.getBytes(StandardCharsets.US_ASCII));
|
||||
return certs.isEmpty() ? null : certs.get(0);
|
||||
} catch (IOException e) {
|
||||
throw new CertificateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@VisibleForTesting(visibility = Visibility.PRIVATE)
|
||||
public static String encodeForIpsecSecret(@NonNull byte[] secret) {
|
||||
checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret");
|
||||
|
||||
return Base64.getEncoder().encodeToString(secret);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static byte[] decodeFromIpsecSecret(@NonNull String encoded) {
|
||||
checkNotNull(encoded, MISSING_PARAM_MSG_TMPL, "encoded");
|
||||
|
||||
return Base64.getDecoder().decode(encoded);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static PrivateKey getPrivateKey(@NonNull String keyStr)
|
||||
throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
final PKCS8EncodedKeySpec privateKeySpec =
|
||||
new PKCS8EncodedKeySpec(decodeFromIpsecSecret(keyStr));
|
||||
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
return keyFactory.generatePrivate(privateKeySpec);
|
||||
}
|
||||
|
||||
private static void checkCert(@NonNull X509Certificate cert) {
|
||||
try {
|
||||
certificateToPemString(cert);
|
||||
} catch (GeneralSecurityException | IOException e) {
|
||||
throw new IllegalArgumentException("Certificate could not be encoded");
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull <T> T checkNotNull(
|
||||
final T reference, final String messageTemplate, final Object... messageArgs) {
|
||||
return Objects.requireNonNull(reference, String.format(messageTemplate, messageArgs));
|
||||
}
|
||||
|
||||
/** A incremental builder for IKEv2 VPN profiles */
|
||||
public static final class Builder {
|
||||
private int mType = -1;
|
||||
@NonNull private final String mServerAddr;
|
||||
@NonNull private final String mUserIdentity;
|
||||
|
||||
// PSK authentication
|
||||
@Nullable private byte[] mPresharedKey;
|
||||
|
||||
// Username/Password, RSA authentication
|
||||
@Nullable private X509Certificate mServerRootCaCert;
|
||||
|
||||
// Username/Password authentication
|
||||
@Nullable private String mUsername;
|
||||
@Nullable private String mPassword;
|
||||
|
||||
// RSA Certificate authentication
|
||||
@Nullable private PrivateKey mRsaPrivateKey;
|
||||
@Nullable private X509Certificate mUserCert;
|
||||
|
||||
@Nullable private ProxyInfo mProxyInfo;
|
||||
@NonNull private List<String> mAllowedAlgorithms = new ArrayList<>();
|
||||
private boolean mIsBypassable = false;
|
||||
private boolean mIsMetered = true;
|
||||
private int mMaxMtu = 1360;
|
||||
|
||||
/**
|
||||
* Creates a new builder with the basic parameters of an IKEv2/IPsec VPN.
|
||||
*
|
||||
* @param serverAddr the server that the VPN should connect to
|
||||
* @param identity the identity string to be used for IKEv2 authentication
|
||||
*/
|
||||
public Builder(@NonNull String serverAddr, @NonNull String identity) {
|
||||
checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "serverAddr");
|
||||
checkNotNull(identity, MISSING_PARAM_MSG_TMPL, "identity");
|
||||
|
||||
mServerAddr = serverAddr;
|
||||
mUserIdentity = identity;
|
||||
}
|
||||
|
||||
private void resetAuthParams() {
|
||||
mPresharedKey = null;
|
||||
mServerRootCaCert = null;
|
||||
mUsername = null;
|
||||
mPassword = null;
|
||||
mRsaPrivateKey = null;
|
||||
mUserCert = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IKEv2 authentication to use the provided username/password.
|
||||
*
|
||||
* <p>Setting this will configure IKEv2 authentication using EAP-MSCHAPv2. Only one
|
||||
* authentication method may be set. This method will overwrite any previously set
|
||||
* authentication method.
|
||||
*
|
||||
* @param user the username to be used for EAP-MSCHAPv2 authentication
|
||||
* @param pass the password to be used for EAP-MSCHAPv2 authentication
|
||||
* @param serverRootCa the root certificate to be used for verifying the identity of the
|
||||
* server
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
* @throws IllegalArgumentException if any of the certificates were invalid or of an
|
||||
* unrecognized format
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAuthUsernamePassword(
|
||||
@NonNull String user,
|
||||
@NonNull String pass,
|
||||
@Nullable X509Certificate serverRootCa) {
|
||||
checkNotNull(user, MISSING_PARAM_MSG_TMPL, "user");
|
||||
checkNotNull(pass, MISSING_PARAM_MSG_TMPL, "pass");
|
||||
|
||||
// Test to make sure all auth params can be encoded safely.
|
||||
if (serverRootCa != null) checkCert(serverRootCa);
|
||||
|
||||
resetAuthParams();
|
||||
mUsername = user;
|
||||
mPassword = pass;
|
||||
mServerRootCaCert = serverRootCa;
|
||||
mType = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IKEv2 authentication to use Digital Signature Authentication with the given key.
|
||||
*
|
||||
* <p>Setting this will configure IKEv2 authentication using a Digital Signature scheme.
|
||||
* Only one authentication method may be set. This method will overwrite any previously set
|
||||
* authentication method.
|
||||
*
|
||||
* @param userCert the username to be used for RSA Digital signiture authentication
|
||||
* @param key the PrivateKey instance associated with the user ceritificate, used for
|
||||
* constructing the signature
|
||||
* @param serverRootCa the root certificate to be used for verifying the identity of the
|
||||
* server
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
* @throws IllegalArgumentException if any of the certificates were invalid or of an
|
||||
* unrecognized format
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAuthDigitalSignature(
|
||||
@NonNull X509Certificate userCert,
|
||||
@NonNull PrivateKey key,
|
||||
@Nullable X509Certificate serverRootCa) {
|
||||
checkNotNull(userCert, MISSING_PARAM_MSG_TMPL, "userCert");
|
||||
checkNotNull(key, MISSING_PARAM_MSG_TMPL, "key");
|
||||
|
||||
// Test to make sure all auth params can be encoded safely.
|
||||
checkCert(userCert);
|
||||
if (serverRootCa != null) checkCert(serverRootCa);
|
||||
|
||||
resetAuthParams();
|
||||
mUserCert = userCert;
|
||||
mRsaPrivateKey = key;
|
||||
mServerRootCaCert = serverRootCa;
|
||||
mType = VpnProfile.TYPE_IKEV2_IPSEC_RSA;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IKEv2 authentication to use Preshared keys.
|
||||
*
|
||||
* <p>Setting this will configure IKEv2 authentication using a Preshared Key. Only one
|
||||
* authentication method may be set. This method will overwrite any previously set
|
||||
* authentication method.
|
||||
*
|
||||
* @param psk the key to be used for Pre-Shared Key authentication
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAuthPsk(@NonNull byte[] psk) {
|
||||
checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk");
|
||||
|
||||
resetAuthParams();
|
||||
mPresharedKey = psk;
|
||||
mType = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether apps can bypass this VPN connection.
|
||||
*
|
||||
* <p>By default, all traffic from apps are forwarded through the VPN interface and it is
|
||||
* not possible for unprivileged apps to side-step the VPN. If a VPN is set to bypassable,
|
||||
* apps may use methods such as {@link Network#getSocketFactory} or {@link
|
||||
* Network#openConnection} to instead send/receive directly over the underlying network or
|
||||
* any other network they have permissions for.
|
||||
*
|
||||
* @param isBypassable Whether or not the VPN should be considered bypassable. Defaults to
|
||||
* {@code false}.
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setBypassable(boolean isBypassable) {
|
||||
mIsBypassable = isBypassable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a proxy for the VPN network.
|
||||
*
|
||||
* <p>Note that this proxy is only a recommendation and it may be ignored by apps.
|
||||
*
|
||||
* @param proxy the ProxyInfo to be set for the VPN network
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setProxy(@Nullable ProxyInfo proxy) {
|
||||
mProxyInfo = proxy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the upper bound of the maximum transmission unit (MTU) of the VPN interface.
|
||||
*
|
||||
* <p>If it is not set, a safe value will be used. Additionally, the actual link MTU will be
|
||||
* dynamically calculated/updated based on the underlying link's mtu.
|
||||
*
|
||||
* @param mtu the MTU (in bytes) of the VPN interface
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
* @throws IllegalArgumentException if the value is not at least the minimum IPv6 MTU (1280)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setMaxMtu(int mtu) {
|
||||
// IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6
|
||||
// networks, the VPN must provide a link fulfilling the stricter of the two conditions
|
||||
// (at least that of the IPv6 MTU).
|
||||
if (mtu < LinkProperties.MIN_MTU_V6) {
|
||||
throw new IllegalArgumentException(
|
||||
"Max MTU must be at least " + LinkProperties.MIN_MTU_V6);
|
||||
}
|
||||
mMaxMtu = mtu;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the VPN network as metered.
|
||||
*
|
||||
* <p>A VPN network is classified as metered when the user is sensitive to heavy data usage
|
||||
* due to monetary costs and/or data limitations. In such cases, you should set this to
|
||||
* {@code true} so that apps on the system can avoid doing large data transfers. Otherwise,
|
||||
* set this to {@code false}. Doing so would cause VPN network to inherit its meteredness
|
||||
* from the underlying network.
|
||||
*
|
||||
* @param isMetered {@code true} if the VPN network should be treated as metered regardless
|
||||
* of underlying network meteredness. Defaults to {@code true}.
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
* @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setMetered(boolean isMetered) {
|
||||
mIsMetered = isMetered;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the allowable set of IPsec algorithms
|
||||
*
|
||||
* <p>A list of allowed IPsec algorithms as defined in {@link IpSecAlgorithm}
|
||||
*
|
||||
* @param algorithmNames the list of supported IPsec algorithms
|
||||
* @return this {@link Builder} object to facilitate chaining of method calls
|
||||
* @see IpSecAlgorithm
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAllowedAlgorithms(@NonNull List<String> algorithmNames) {
|
||||
checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames");
|
||||
VpnProfile.validateAllowedAlgorithms(algorithmNames);
|
||||
|
||||
mAllowedAlgorithms = algorithmNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates, builds and provisions the VpnProfile.
|
||||
*
|
||||
* @throws IllegalArgumentException if any of the required keys or values were invalid
|
||||
*/
|
||||
@NonNull
|
||||
public Ikev2VpnProfile build() {
|
||||
return new Ikev2VpnProfile(
|
||||
mType,
|
||||
mServerAddr,
|
||||
mUserIdentity,
|
||||
mPresharedKey,
|
||||
mServerRootCaCert,
|
||||
mUsername,
|
||||
mPassword,
|
||||
mRsaPrivateKey,
|
||||
mUserCert,
|
||||
mProxyInfo,
|
||||
mAllowedAlgorithms,
|
||||
mIsBypassable,
|
||||
mIsMetered,
|
||||
mMaxMtu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,8 @@ public final class LinkProperties implements Parcelable {
|
||||
private final transient boolean mParcelSensitiveFields;
|
||||
|
||||
private static final int MIN_MTU = 68;
|
||||
private static final int MIN_MTU_V6 = 1280;
|
||||
/* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */
|
||||
static final int MIN_MTU_V6 = 1280;
|
||||
private static final int MAX_MTU = 10000;
|
||||
|
||||
private static final int INET6_ADDR_LENGTH = 16;
|
||||
|
||||
107
core/java/android/net/PlatformVpnProfile.java
Normal file
107
core/java/android/net/PlatformVpnProfile.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK;
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA;
|
||||
import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* PlatformVpnProfile represents a configuration for a platform-based VPN implementation.
|
||||
*
|
||||
* <p>Platform-based VPNs allow VPN applications to provide configuration and authentication options
|
||||
* to leverage the Android OS' implementations of well-defined control plane (authentication, key
|
||||
* negotiation) and data plane (per-packet encryption) protocols to simplify the creation of VPN
|
||||
* tunnels. In contrast, {@link VpnService} based VPNs must implement both the control and data
|
||||
* planes on a per-app basis.
|
||||
*
|
||||
* @see Ikev2VpnProfile
|
||||
*/
|
||||
public abstract class PlatformVpnProfile {
|
||||
/**
|
||||
* Alias to platform VPN related types from VpnProfile, for API use.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
TYPE_IKEV2_IPSEC_USER_PASS,
|
||||
TYPE_IKEV2_IPSEC_PSK,
|
||||
TYPE_IKEV2_IPSEC_RSA,
|
||||
})
|
||||
public static @interface PlatformVpnType {}
|
||||
|
||||
public static final int TYPE_IKEV2_IPSEC_USER_PASS = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
public static final int TYPE_IKEV2_IPSEC_PSK = VpnProfile.TYPE_IKEV2_IPSEC_PSK;
|
||||
public static final int TYPE_IKEV2_IPSEC_RSA = VpnProfile.TYPE_IKEV2_IPSEC_RSA;
|
||||
|
||||
/** @hide */
|
||||
@PlatformVpnType protected final int mType;
|
||||
|
||||
/** @hide */
|
||||
PlatformVpnProfile(@PlatformVpnType int type) {
|
||||
mType = type;
|
||||
}
|
||||
/** Returns the profile integer type. */
|
||||
@PlatformVpnType
|
||||
public final int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/** Returns a type string describing the VPN profile type */
|
||||
@NonNull
|
||||
public final String getTypeString() {
|
||||
switch (mType) {
|
||||
case TYPE_IKEV2_IPSEC_USER_PASS:
|
||||
return "IKEv2/IPsec Username/Password";
|
||||
case TYPE_IKEV2_IPSEC_PSK:
|
||||
return "IKEv2/IPsec Preshared key";
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
return "IKEv2/IPsec RSA Digital Signature";
|
||||
default:
|
||||
return "Unknown VPN profile type";
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public abstract VpnProfile toVpnProfile() throws IOException, GeneralSecurityException;
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public static PlatformVpnProfile fromVpnProfile(@NonNull VpnProfile profile)
|
||||
throws IOException, GeneralSecurityException {
|
||||
switch (profile.type) {
|
||||
case TYPE_IKEV2_IPSEC_USER_PASS: // fallthrough
|
||||
case TYPE_IKEV2_IPSEC_PSK: // fallthrough
|
||||
case TYPE_IKEV2_IPSEC_RSA:
|
||||
return Ikev2VpnProfile.fromVpnProfile(profile);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown VPN Profile type");
|
||||
}
|
||||
}
|
||||
}
|
||||
88
core/java/android/net/VpnManager.java
Normal file
88
core/java/android/net/VpnManager.java
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkNotNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
/**
|
||||
* This class provides an interface for apps to manage platform VPN profiles
|
||||
*
|
||||
* <p>Apps can use this API to provide profiles with which the platform can set up a VPN without
|
||||
* further app intermediation. When a VPN profile is present and the app is selected as an always-on
|
||||
* VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the
|
||||
* app (unlike VpnService).
|
||||
*
|
||||
* <p>VPN apps using supported protocols should preferentially use this API over the {@link
|
||||
* VpnService} API for ease-of-development and reduced maintainance burden. This also give the user
|
||||
* the guarantee that VPN network traffic is not subjected to on-device packet interception.
|
||||
*
|
||||
* @see Ikev2VpnProfile
|
||||
*/
|
||||
public class VpnManager {
|
||||
@NonNull private final Context mContext;
|
||||
@NonNull private final IConnectivityManager mService;
|
||||
|
||||
/**
|
||||
* Create an instance of the VpnManger with the given context.
|
||||
*
|
||||
* <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the
|
||||
* {@link Context.getSystemService()} method call.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) {
|
||||
mContext = checkNotNull(ctx, "missing Context");
|
||||
mService = checkNotNull(service, "missing IConnectivityManager");
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a VpnProfile configuration keyed on the calling app's package name.
|
||||
*
|
||||
* @param profile the PlatformVpnProfile provided by this package. Will override any previous
|
||||
* PlatformVpnProfile stored for this package.
|
||||
* @return an intent to request user consent if needed (null otherwise).
|
||||
*/
|
||||
@Nullable
|
||||
public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
/** Delete the VPN profile configuration that was provisioned by the calling app */
|
||||
public void deleteProvisionedVpnProfile() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the startup of a previously provisioned VPN.
|
||||
*
|
||||
* @throws SecurityException exception if user or device settings prevent this VPN from being
|
||||
* setup, or if user consent has not been granted
|
||||
*/
|
||||
public void startProvisionedVpnProfile() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
/** Tear down the VPN provided by the calling app (if any) */
|
||||
public void stopProvisionedVpnProfile() {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.internal.net;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.net.ProxyInfo;
|
||||
import android.os.Build;
|
||||
@@ -23,21 +24,34 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Parcel-like entity class for VPN profiles. To keep things simple, all
|
||||
* fields are package private. Methods are provided for serialization, so
|
||||
* storage can be implemented easily. Two rules are set for this class.
|
||||
* First, all fields must be kept non-null. Second, always make a copy
|
||||
* using clone() before modifying.
|
||||
* Profile storage class for a platform VPN.
|
||||
*
|
||||
* <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs
|
||||
* (such as IKEv2/IPsec).
|
||||
*
|
||||
* <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()}
|
||||
* functions for persistent storage in the Android Keystore. The encoding is entirely custom, but
|
||||
* must be kept for backward compatibility for devices upgrading between Android versions.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class VpnProfile implements Cloneable, Parcelable {
|
||||
public final class VpnProfile implements Cloneable, Parcelable {
|
||||
private static final String TAG = "VpnProfile";
|
||||
|
||||
@VisibleForTesting static final String VALUE_DELIMITER = "\0";
|
||||
@VisibleForTesting static final String LIST_DELIMITER = ",";
|
||||
|
||||
// Match these constants with R.array.vpn_types.
|
||||
public static final int TYPE_PPTP = 0;
|
||||
public static final int TYPE_L2TP_IPSEC_PSK = 1;
|
||||
@@ -45,39 +59,85 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
public static final int TYPE_IPSEC_XAUTH_PSK = 3;
|
||||
public static final int TYPE_IPSEC_XAUTH_RSA = 4;
|
||||
public static final int TYPE_IPSEC_HYBRID_RSA = 5;
|
||||
public static final int TYPE_MAX = 5;
|
||||
public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6;
|
||||
public static final int TYPE_IKEV2_IPSEC_PSK = 7;
|
||||
public static final int TYPE_IKEV2_IPSEC_RSA = 8;
|
||||
public static final int TYPE_MAX = 8;
|
||||
|
||||
// Match these constants with R.array.vpn_proxy_settings.
|
||||
public static final int PROXY_NONE = 0;
|
||||
public static final int PROXY_MANUAL = 1;
|
||||
|
||||
private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0";
|
||||
|
||||
// Entity fields.
|
||||
@UnsupportedAppUsage
|
||||
public final String key; // -1
|
||||
public final String key; // -1
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public String name = ""; // 0
|
||||
public String name = ""; // 0
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public int type = TYPE_PPTP; // 1
|
||||
public int type = TYPE_PPTP; // 1
|
||||
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
||||
public String server = ""; // 2
|
||||
public String server = ""; // 2
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public String username = ""; // 3
|
||||
public String password = ""; // 4
|
||||
public String dnsServers = ""; // 5
|
||||
public String searchDomains = ""; // 6
|
||||
public String routes = ""; // 7
|
||||
public boolean mppe = true; // 8
|
||||
public String l2tpSecret = ""; // 9
|
||||
public String ipsecIdentifier = "";// 10
|
||||
public String ipsecSecret = ""; // 11
|
||||
public String ipsecUserCert = ""; // 12
|
||||
public String ipsecCaCert = ""; // 13
|
||||
public String ipsecServerCert = "";// 14
|
||||
public ProxyInfo proxy = null; // 15~18
|
||||
public String username = ""; // 3
|
||||
public String password = ""; // 4
|
||||
public String dnsServers = ""; // 5
|
||||
public String searchDomains = ""; // 6
|
||||
public String routes = ""; // 7
|
||||
public boolean mppe = true; // 8
|
||||
public String l2tpSecret = ""; // 9
|
||||
public String ipsecIdentifier = ""; // 10
|
||||
|
||||
/**
|
||||
* The RSA private key or pre-shared key used for authentication.
|
||||
*
|
||||
* <p>If areAuthParamsInline is {@code true}, this String will be either:
|
||||
*
|
||||
* <ul>
|
||||
* <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey}
|
||||
* <li>If this is an IKEv2 PSK profile: a string value representing the PSK.
|
||||
* </ul>
|
||||
*/
|
||||
public String ipsecSecret = ""; // 11
|
||||
|
||||
/**
|
||||
* The RSA certificate to be used for digital signature authentication.
|
||||
*
|
||||
* <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
|
||||
* java.security.X509Certificate}
|
||||
*/
|
||||
public String ipsecUserCert = ""; // 12
|
||||
|
||||
/**
|
||||
* The RSA certificate that should be used to verify the server's end/target certificate.
|
||||
*
|
||||
* <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link
|
||||
* java.security.X509Certificate}
|
||||
*/
|
||||
public String ipsecCaCert = ""; // 13
|
||||
public String ipsecServerCert = ""; // 14
|
||||
public ProxyInfo proxy = null; // 15~18
|
||||
|
||||
/**
|
||||
* The list of allowable algorithms.
|
||||
*
|
||||
* <p>This list is validated in the setter to ensure that encoding characters (list, value
|
||||
* delimiters) are not present in the algorithm names. See {@link #validateAllowedAlgorithms()}
|
||||
*/
|
||||
private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19
|
||||
public boolean isBypassable = false; // 20
|
||||
public boolean isMetered = false; // 21
|
||||
public int maxMtu = 1400; // 22
|
||||
public boolean areAuthParamsInline = false; // 23
|
||||
|
||||
// Helper fields.
|
||||
@UnsupportedAppUsage
|
||||
public boolean saveLogin = false;
|
||||
public transient boolean saveLogin = false;
|
||||
|
||||
public VpnProfile(String key) {
|
||||
this.key = key;
|
||||
@@ -103,6 +163,34 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
ipsecServerCert = in.readString();
|
||||
saveLogin = in.readInt() != 0;
|
||||
proxy = in.readParcelable(null);
|
||||
mAllowedAlgorithms = new ArrayList<>();
|
||||
in.readList(mAllowedAlgorithms, null);
|
||||
isBypassable = in.readBoolean();
|
||||
isMetered = in.readBoolean();
|
||||
maxMtu = in.readInt();
|
||||
areAuthParamsInline = in.readBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of allowed algorithms.
|
||||
*
|
||||
* <p>The contained elements are as listed in {@link IpSecAlgorithm}
|
||||
*/
|
||||
public List<String> getAllowedAlgorithms() {
|
||||
return Collections.unmodifiableList(mAllowedAlgorithms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sets the list of algorithms that can be used for the IPsec transforms.
|
||||
*
|
||||
* @param allowedAlgorithms the list of allowable algorithms, as listed in {@link
|
||||
* IpSecAlgorithm}.
|
||||
* @throws IllegalArgumentException if any delimiters are used in algorithm names. See {@link
|
||||
* #VALUE_DELIMITER} and {@link LIST_DELIMITER}.
|
||||
*/
|
||||
public void setAllowedAlgorithms(List<String> allowedAlgorithms) {
|
||||
validateAllowedAlgorithms(allowedAlgorithms);
|
||||
mAllowedAlgorithms = allowedAlgorithms;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,8 +213,18 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
out.writeString(ipsecServerCert);
|
||||
out.writeInt(saveLogin ? 1 : 0);
|
||||
out.writeParcelable(proxy, flags);
|
||||
out.writeList(mAllowedAlgorithms);
|
||||
out.writeBoolean(isBypassable);
|
||||
out.writeBoolean(isMetered);
|
||||
out.writeInt(maxMtu);
|
||||
out.writeBoolean(areAuthParamsInline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a VpnProfile instance from the encoded byte array.
|
||||
*
|
||||
* <p>See {@link #encode()}
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static VpnProfile decode(String key, byte[] value) {
|
||||
try {
|
||||
@@ -134,9 +232,11 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] values = new String(value, StandardCharsets.UTF_8).split("\0", -1);
|
||||
// There can be 14 - 19 Bytes in values.length.
|
||||
if (values.length < 14 || values.length > 19) {
|
||||
String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1);
|
||||
// Acceptable numbers of values are:
|
||||
// 14-19: Standard profile, with option for serverCert, proxy
|
||||
// 24: Standard profile with serverCert, proxy and platform-VPN parameters.
|
||||
if ((values.length < 14 || values.length > 19) && values.length != 24) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -164,13 +264,23 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
String port = (values.length > 16) ? values[16] : "";
|
||||
String exclList = (values.length > 17) ? values[17] : "";
|
||||
String pacFileUrl = (values.length > 18) ? values[18] : "";
|
||||
if (pacFileUrl.isEmpty()) {
|
||||
if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) {
|
||||
profile.proxy = new ProxyInfo(host, port.isEmpty() ?
|
||||
0 : Integer.parseInt(port), exclList);
|
||||
} else {
|
||||
} else if (!pacFileUrl.isEmpty()) {
|
||||
profile.proxy = new ProxyInfo(pacFileUrl);
|
||||
}
|
||||
} // else profle.proxy = null
|
||||
} // else profile.proxy = null
|
||||
|
||||
// Either all must be present, or none must be.
|
||||
if (values.length >= 24) {
|
||||
profile.mAllowedAlgorithms = Arrays.asList(values[19].split(LIST_DELIMITER));
|
||||
profile.isBypassable = Boolean.parseBoolean(values[20]);
|
||||
profile.isMetered = Boolean.parseBoolean(values[21]);
|
||||
profile.maxMtu = Integer.parseInt(values[22]);
|
||||
profile.areAuthParamsInline = Boolean.parseBoolean(values[23]);
|
||||
}
|
||||
|
||||
profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
|
||||
return profile;
|
||||
} catch (Exception e) {
|
||||
@@ -179,36 +289,52 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a VpnProfile instance to a byte array for storage.
|
||||
*
|
||||
* <p>See {@link #decode(String, byte[])}
|
||||
*/
|
||||
public byte[] encode() {
|
||||
StringBuilder builder = new StringBuilder(name);
|
||||
builder.append('\0').append(type);
|
||||
builder.append('\0').append(server);
|
||||
builder.append('\0').append(saveLogin ? username : "");
|
||||
builder.append('\0').append(saveLogin ? password : "");
|
||||
builder.append('\0').append(dnsServers);
|
||||
builder.append('\0').append(searchDomains);
|
||||
builder.append('\0').append(routes);
|
||||
builder.append('\0').append(mppe);
|
||||
builder.append('\0').append(l2tpSecret);
|
||||
builder.append('\0').append(ipsecIdentifier);
|
||||
builder.append('\0').append(ipsecSecret);
|
||||
builder.append('\0').append(ipsecUserCert);
|
||||
builder.append('\0').append(ipsecCaCert);
|
||||
builder.append('\0').append(ipsecServerCert);
|
||||
builder.append(VALUE_DELIMITER).append(type);
|
||||
builder.append(VALUE_DELIMITER).append(server);
|
||||
builder.append(VALUE_DELIMITER).append(saveLogin ? username : "");
|
||||
builder.append(VALUE_DELIMITER).append(saveLogin ? password : "");
|
||||
builder.append(VALUE_DELIMITER).append(dnsServers);
|
||||
builder.append(VALUE_DELIMITER).append(searchDomains);
|
||||
builder.append(VALUE_DELIMITER).append(routes);
|
||||
builder.append(VALUE_DELIMITER).append(mppe);
|
||||
builder.append(VALUE_DELIMITER).append(l2tpSecret);
|
||||
builder.append(VALUE_DELIMITER).append(ipsecIdentifier);
|
||||
builder.append(VALUE_DELIMITER).append(ipsecSecret);
|
||||
builder.append(VALUE_DELIMITER).append(ipsecUserCert);
|
||||
builder.append(VALUE_DELIMITER).append(ipsecCaCert);
|
||||
builder.append(VALUE_DELIMITER).append(ipsecServerCert);
|
||||
if (proxy != null) {
|
||||
builder.append('\0').append(proxy.getHost() != null ? proxy.getHost() : "");
|
||||
builder.append('\0').append(proxy.getPort());
|
||||
builder.append('\0').append(proxy.getExclusionListAsString() != null ?
|
||||
proxy.getExclusionListAsString() : "");
|
||||
builder.append('\0').append(proxy.getPacFileUrl().toString());
|
||||
builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : "");
|
||||
builder.append(VALUE_DELIMITER).append(proxy.getPort());
|
||||
builder.append(VALUE_DELIMITER)
|
||||
.append(
|
||||
proxy.getExclusionListAsString() != null
|
||||
? proxy.getExclusionListAsString()
|
||||
: "");
|
||||
builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString());
|
||||
} else {
|
||||
builder.append(ENCODED_NULL_PROXY_INFO);
|
||||
}
|
||||
|
||||
builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, mAllowedAlgorithms));
|
||||
builder.append(VALUE_DELIMITER).append(isBypassable);
|
||||
builder.append(VALUE_DELIMITER).append(isMetered);
|
||||
builder.append(VALUE_DELIMITER).append(maxMtu);
|
||||
builder.append(VALUE_DELIMITER).append(areAuthParamsInline);
|
||||
|
||||
return builder.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if profile is valid for lockdown, which requires IPv4 address for
|
||||
* both server and DNS. Server hostnames would require using DNS before
|
||||
* connection.
|
||||
* Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS.
|
||||
* Server hostnames would require using DNS before connection.
|
||||
*/
|
||||
public boolean isValidLockdownProfile() {
|
||||
return isTypeValidForLockdown()
|
||||
@@ -238,10 +364,7 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
return !TextUtils.isEmpty(dnsServers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if all DNS servers have numeric addresses,
|
||||
* e.g. 8.8.8.8
|
||||
*/
|
||||
/** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */
|
||||
public boolean areDnsAddressesNumeric() {
|
||||
try {
|
||||
for (String dnsServer : dnsServers.split(" +")) {
|
||||
@@ -253,6 +376,62 @@ public class VpnProfile implements Cloneable, Parcelable {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the provided list of algorithms does not contain illegal characters.
|
||||
*
|
||||
* @param allowedAlgorithms The list to be validated
|
||||
*/
|
||||
public static void validateAllowedAlgorithms(List<String> allowedAlgorithms) {
|
||||
for (final String alg : allowedAlgorithms) {
|
||||
if (alg.contains(VALUE_DELIMITER) || alg.contains(LIST_DELIMITER)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Algorithm contained illegal ('\0' or ',') character");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates a hashcode over the VpnProfile. */
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
|
||||
l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
|
||||
proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline);
|
||||
}
|
||||
|
||||
/** Checks VPN profiles for interior equality. */
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof VpnProfile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final VpnProfile other = (VpnProfile) obj;
|
||||
return Objects.equals(key, other.key)
|
||||
&& Objects.equals(name, other.name)
|
||||
&& type == other.type
|
||||
&& Objects.equals(server, other.server)
|
||||
&& Objects.equals(username, other.username)
|
||||
&& Objects.equals(password, other.password)
|
||||
&& Objects.equals(dnsServers, other.dnsServers)
|
||||
&& Objects.equals(searchDomains, other.searchDomains)
|
||||
&& Objects.equals(routes, other.routes)
|
||||
&& mppe == other.mppe
|
||||
&& Objects.equals(l2tpSecret, other.l2tpSecret)
|
||||
&& Objects.equals(ipsecIdentifier, other.ipsecIdentifier)
|
||||
&& Objects.equals(ipsecSecret, other.ipsecSecret)
|
||||
&& Objects.equals(ipsecUserCert, other.ipsecUserCert)
|
||||
&& Objects.equals(ipsecCaCert, other.ipsecCaCert)
|
||||
&& Objects.equals(ipsecServerCert, other.ipsecServerCert)
|
||||
&& Objects.equals(proxy, other.proxy)
|
||||
&& Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
|
||||
&& isBypassable == other.isBypassable
|
||||
&& isMetered == other.isMetered
|
||||
&& maxMtu == other.maxMtu
|
||||
&& areAuthParamsInline == other.areAuthParamsInline;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() {
|
||||
@Override
|
||||
public VpnProfile createFromParcel(Parcel in) {
|
||||
|
||||
360
tests/net/java/android/net/Ikev2VpnProfileTest.java
Normal file
360
tests/net/java/android/net/Ikev2VpnProfileTest.java
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* 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 org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import android.test.mock.MockContext;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.net.VpnProfile;
|
||||
import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class Ikev2VpnProfileTest {
|
||||
private static final String SERVER_ADDR_STRING = "1.2.3.4";
|
||||
private static final String IDENTITY_STRING = "Identity";
|
||||
private static final String USERNAME_STRING = "username";
|
||||
private static final String PASSWORD_STRING = "pa55w0rd";
|
||||
private static final String EXCL_LIST = "exclList";
|
||||
private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
|
||||
private static final int TEST_MTU = 1300;
|
||||
|
||||
private final MockContext mMockContext =
|
||||
new MockContext() {
|
||||
@Override
|
||||
public String getOpPackageName() {
|
||||
return "fooPackage";
|
||||
}
|
||||
};
|
||||
private final ProxyInfo mProxy = new ProxyInfo(SERVER_ADDR_STRING, -1, EXCL_LIST);
|
||||
|
||||
private X509Certificate mUserCert;
|
||||
private X509Certificate mServerRootCa;
|
||||
private PrivateKey mPrivateKey;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mServerRootCa = generateRandomCertAndKeyPair().cert;
|
||||
|
||||
final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
|
||||
mUserCert = userCertKey.cert;
|
||||
mPrivateKey = userCertKey.key;
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
|
||||
final Ikev2VpnProfile.Builder builder =
|
||||
new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
|
||||
|
||||
builder.setBypassable(true);
|
||||
builder.setProxy(mProxy);
|
||||
builder.setMaxMtu(TEST_MTU);
|
||||
builder.setMetered(true);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildValidProfileWithOptions() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
|
||||
final Ikev2VpnProfile profile = builder.build();
|
||||
assertNotNull(profile);
|
||||
|
||||
// Check non-auth parameters correctly stored
|
||||
assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
|
||||
assertEquals(IDENTITY_STRING, profile.getUserIdentity());
|
||||
assertEquals(mProxy, profile.getProxyInfo());
|
||||
assertTrue(profile.isBypassable());
|
||||
assertTrue(profile.isMetered());
|
||||
assertEquals(TEST_MTU, profile.getMaxMtu());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildUsernamePasswordProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
|
||||
final Ikev2VpnProfile profile = builder.build();
|
||||
assertNotNull(profile);
|
||||
|
||||
assertEquals(USERNAME_STRING, profile.getUsername());
|
||||
assertEquals(PASSWORD_STRING, profile.getPassword());
|
||||
assertEquals(mServerRootCa, profile.getServerRootCaCert());
|
||||
|
||||
assertNull(profile.getPresharedKey());
|
||||
assertNull(profile.getRsaPrivateKey());
|
||||
assertNull(profile.getUserCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildDigitalSignatureProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
|
||||
final Ikev2VpnProfile profile = builder.build();
|
||||
assertNotNull(profile);
|
||||
|
||||
assertEquals(profile.getUserCert(), mUserCert);
|
||||
assertEquals(mPrivateKey, profile.getRsaPrivateKey());
|
||||
assertEquals(profile.getServerRootCaCert(), mServerRootCa);
|
||||
|
||||
assertNull(profile.getPresharedKey());
|
||||
assertNull(profile.getUsername());
|
||||
assertNull(profile.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildPresharedKeyProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthPsk(PSK_BYTES);
|
||||
final Ikev2VpnProfile profile = builder.build();
|
||||
assertNotNull(profile);
|
||||
|
||||
assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
|
||||
|
||||
assertNull(profile.getServerRootCaCert());
|
||||
assertNull(profile.getUsername());
|
||||
assertNull(profile.getPassword());
|
||||
assertNull(profile.getRsaPrivateKey());
|
||||
assertNull(profile.getUserCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildNoAuthMethodSet() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
try {
|
||||
builder.build();
|
||||
fail("Expected exception due to lack of auth method");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildInvalidMtu() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
try {
|
||||
builder.setMaxMtu(500);
|
||||
fail("Expected exception due to too-small MTU");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyVpnProfileCommon(VpnProfile profile) {
|
||||
assertEquals(SERVER_ADDR_STRING, profile.server);
|
||||
assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
|
||||
assertEquals(mProxy, profile.proxy);
|
||||
assertTrue(profile.isBypassable);
|
||||
assertTrue(profile.isMetered);
|
||||
assertEquals(TEST_MTU, profile.maxMtu);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPskConvertToVpnProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthPsk(PSK_BYTES);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
|
||||
verifyVpnProfileCommon(profile);
|
||||
assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
|
||||
|
||||
// Check nothing else is set
|
||||
assertEquals("", profile.username);
|
||||
assertEquals("", profile.password);
|
||||
assertEquals("", profile.ipsecUserCert);
|
||||
assertEquals("", profile.ipsecCaCert);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsernamePasswordConvertToVpnProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
|
||||
verifyVpnProfileCommon(profile);
|
||||
assertEquals(USERNAME_STRING, profile.username);
|
||||
assertEquals(PASSWORD_STRING, profile.password);
|
||||
assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
|
||||
|
||||
// Check nothing else is set
|
||||
assertEquals("", profile.ipsecUserCert);
|
||||
assertEquals("", profile.ipsecSecret);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaConvertToVpnProfile() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
|
||||
verifyVpnProfileCommon(profile);
|
||||
assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
|
||||
assertEquals(
|
||||
Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()),
|
||||
profile.ipsecSecret);
|
||||
assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
|
||||
|
||||
// Check nothing else is set
|
||||
assertEquals("", profile.username);
|
||||
assertEquals("", profile.password);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthPsk(PSK_BYTES);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
profile.username = USERNAME_STRING;
|
||||
profile.password = PASSWORD_STRING;
|
||||
profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
|
||||
profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
|
||||
|
||||
final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
|
||||
assertNull(result.getUsername());
|
||||
assertNull(result.getPassword());
|
||||
assertNull(result.getUserCert());
|
||||
assertNull(result.getRsaPrivateKey());
|
||||
assertNull(result.getServerRootCaCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
profile.ipsecSecret = new String(PSK_BYTES);
|
||||
profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
|
||||
|
||||
final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
|
||||
assertNull(result.getPresharedKey());
|
||||
assertNull(result.getUserCert());
|
||||
assertNull(result.getRsaPrivateKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
|
||||
final VpnProfile profile = builder.build().toVpnProfile();
|
||||
profile.username = USERNAME_STRING;
|
||||
profile.password = PASSWORD_STRING;
|
||||
|
||||
final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
|
||||
assertNull(result.getUsername());
|
||||
assertNull(result.getPassword());
|
||||
assertNull(result.getPresharedKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPskConversionIsLossless() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthPsk(PSK_BYTES);
|
||||
final Ikev2VpnProfile ikeProfile = builder.build();
|
||||
|
||||
assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUsernamePasswordConversionIsLossless() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
|
||||
final Ikev2VpnProfile ikeProfile = builder.build();
|
||||
|
||||
assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaConversionIsLossless() throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
|
||||
|
||||
builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
|
||||
final Ikev2VpnProfile ikeProfile = builder.build();
|
||||
|
||||
assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
|
||||
}
|
||||
|
||||
private static class CertificateAndKey {
|
||||
public final X509Certificate cert;
|
||||
public final PrivateKey key;
|
||||
|
||||
CertificateAndKey(X509Certificate cert, PrivateKey key) {
|
||||
this.cert = cert;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
|
||||
final Date validityBeginDate =
|
||||
new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
|
||||
final Date validityEndDate =
|
||||
new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
|
||||
|
||||
// Generate a keypair
|
||||
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(512);
|
||||
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
|
||||
final X500Principal dnName = new X500Principal("CN=test.android.com");
|
||||
final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
|
||||
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
|
||||
certGen.setSubjectDN(dnName);
|
||||
certGen.setIssuerDN(dnName);
|
||||
certGen.setNotBefore(validityBeginDate);
|
||||
certGen.setNotAfter(validityEndDate);
|
||||
certGen.setPublicKey(keyPair.getPublic());
|
||||
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
|
||||
|
||||
final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
|
||||
return new CertificateAndKey(cert, keyPair.getPrivate());
|
||||
}
|
||||
}
|
||||
83
tests/net/java/android/net/VpnManagerTest.java
Normal file
83
tests/net/java/android/net/VpnManagerTest.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 org.mockito.Mockito.mock;
|
||||
|
||||
import android.test.mock.MockContext;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link VpnManager}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class VpnManagerTest {
|
||||
private static final String VPN_PROFILE_KEY = "KEY";
|
||||
|
||||
private IConnectivityManager mMockCs;
|
||||
private VpnManager mVpnManager;
|
||||
private final MockContext mMockContext =
|
||||
new MockContext() {
|
||||
@Override
|
||||
public String getOpPackageName() {
|
||||
return "fooPackage";
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mMockCs = mock(IConnectivityManager.class);
|
||||
mVpnManager = new VpnManager(mMockContext, mMockCs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionVpnProfile() throws Exception {
|
||||
try {
|
||||
mVpnManager.provisionVpnProfile(mock(PlatformVpnProfile.class));
|
||||
} catch (UnsupportedOperationException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteProvisionedVpnProfile() throws Exception {
|
||||
try {
|
||||
mVpnManager.deleteProvisionedVpnProfile();
|
||||
} catch (UnsupportedOperationException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartProvisionedVpnProfile() throws Exception {
|
||||
try {
|
||||
mVpnManager.startProvisionedVpnProfile();
|
||||
} catch (UnsupportedOperationException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStopProvisionedVpnProfile() throws Exception {
|
||||
try {
|
||||
mVpnManager.stopProvisionedVpnProfile();
|
||||
} catch (UnsupportedOperationException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
185
tests/net/java/com/android/internal/net/VpnProfileTest.java
Normal file
185
tests/net/java/com/android/internal/net/VpnProfileTest.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 com.android.internal.net;
|
||||
|
||||
import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.IpSecAlgorithm;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Unit tests for {@link VpnProfile}. */
|
||||
@SmallTest
|
||||
@RunWith(JUnit4.class)
|
||||
public class VpnProfileTest {
|
||||
private static final String DUMMY_PROFILE_KEY = "Test";
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws Exception {
|
||||
final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
|
||||
|
||||
assertEquals(DUMMY_PROFILE_KEY, p.key);
|
||||
assertEquals("", p.name);
|
||||
assertEquals(VpnProfile.TYPE_PPTP, p.type);
|
||||
assertEquals("", p.server);
|
||||
assertEquals("", p.username);
|
||||
assertEquals("", p.password);
|
||||
assertEquals("", p.dnsServers);
|
||||
assertEquals("", p.searchDomains);
|
||||
assertEquals("", p.routes);
|
||||
assertTrue(p.mppe);
|
||||
assertEquals("", p.l2tpSecret);
|
||||
assertEquals("", p.ipsecIdentifier);
|
||||
assertEquals("", p.ipsecSecret);
|
||||
assertEquals("", p.ipsecUserCert);
|
||||
assertEquals("", p.ipsecCaCert);
|
||||
assertEquals("", p.ipsecServerCert);
|
||||
assertEquals(null, p.proxy);
|
||||
assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
|
||||
assertFalse(p.isBypassable);
|
||||
assertFalse(p.isMetered);
|
||||
assertEquals(1400, p.maxMtu);
|
||||
assertFalse(p.areAuthParamsInline);
|
||||
}
|
||||
|
||||
private VpnProfile getSampleIkev2Profile(String key) {
|
||||
final VpnProfile p = new VpnProfile(key);
|
||||
|
||||
p.name = "foo";
|
||||
p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
|
||||
p.server = "bar";
|
||||
p.username = "baz";
|
||||
p.password = "qux";
|
||||
p.dnsServers = "8.8.8.8";
|
||||
p.searchDomains = "";
|
||||
p.routes = "0.0.0.0/0";
|
||||
p.mppe = false;
|
||||
p.l2tpSecret = "";
|
||||
p.ipsecIdentifier = "quux";
|
||||
p.ipsecSecret = "quuz";
|
||||
p.ipsecUserCert = "corge";
|
||||
p.ipsecCaCert = "grault";
|
||||
p.ipsecServerCert = "garply";
|
||||
p.proxy = null;
|
||||
p.setAllowedAlgorithms(
|
||||
Arrays.asList(
|
||||
IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
|
||||
IpSecAlgorithm.AUTH_HMAC_SHA512,
|
||||
IpSecAlgorithm.CRYPT_AES_CBC));
|
||||
p.isBypassable = true;
|
||||
p.isMetered = true;
|
||||
p.maxMtu = 1350;
|
||||
p.areAuthParamsInline = true;
|
||||
|
||||
// Not saved, but also not compared.
|
||||
p.saveLogin = true;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertEquals(
|
||||
getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
|
||||
|
||||
final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
modified.maxMtu--;
|
||||
assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParcelUnparcel() {
|
||||
assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetInvalidAlgorithmValueDelimiter() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
|
||||
try {
|
||||
profile.setAllowedAlgorithms(
|
||||
Arrays.asList("test" + VpnProfile.VALUE_DELIMITER + "test"));
|
||||
fail("Expected failure due to value separator in algorithm name");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetInvalidAlgorithmListDelimiter() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
|
||||
try {
|
||||
profile.setAllowedAlgorithms(
|
||||
Arrays.asList("test" + VpnProfile.LIST_DELIMITER + "test"));
|
||||
fail("Expected failure due to value separator in algorithm name");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecode() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
|
||||
assertEquals(profile, decoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeTooManyValues() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
final byte[] tooManyValues =
|
||||
(new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
|
||||
|
||||
assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeInvalidNumberOfValues() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
final String encoded = new String(profile.encode());
|
||||
final byte[] tooFewValues =
|
||||
encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes();
|
||||
|
||||
assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeLoginsNotSaved() {
|
||||
final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
|
||||
profile.saveLogin = false;
|
||||
|
||||
final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
|
||||
assertNotEquals(profile, decoded);
|
||||
|
||||
// Add the username/password back, everything else must be equal.
|
||||
decoded.username = profile.username;
|
||||
decoded.password = profile.password;
|
||||
assertEquals(profile, decoded);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user