Remove old VPN tests which instrumented Settings

This is not actually a Settings test and never was, it just used
Settings as a convenient delivery mechanism for system-level privileges.

Tests for the Settings app should only check that the Settings app
is behaving within its remit, ie. showing system server state and making
the appropriate calls to set up a connection when certain UI elements
are pressed.

Also since the test relies heavily on off-device state it makes a lot
more sense to run as a hostside test and have the same test code
responsible for pushing certificates and network configs to the device
instead of including manual instructions.

For an end-to-end legacy VPN test see ACTS: https://android.googlesource.com/platform/tools/test/connectivity/+/master/acts/tests/google/net/LegacyVpnTest.py
For an end-to-end app VPN test see CTS hostside: https://android.googlesource.com/platform/cts/+/master/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/AlwaysOnVpnTest.java

Test: make Settings
Change-Id: Ia105c3ebd2fa5064adc4422bd3d77c3abba55634
Fix: 64040865
This commit is contained in:
Robin Lee
2017-08-07 10:56:32 +02:00
parent c16f7d55a2
commit 13e64c9c10
4 changed files with 0 additions and 980 deletions

View File

@@ -1,223 +0,0 @@
/*
* Copyright (C) 2013 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.settings.vpn2;
import android.os.Environment;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import com.android.internal.net.VpnProfile;
import com.android.org.bouncycastle.asn1.ASN1InputStream;
import com.android.org.bouncycastle.asn1.ASN1Sequence;
import com.android.org.bouncycastle.asn1.DEROctetString;
import com.android.org.bouncycastle.asn1.x509.BasicConstraints;
import junit.framework.Assert;
import libcore.io.Streams;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
/**
* Certificate installer helper to extract information from a provided file
* and install certificates to keystore.
*/
public class CertInstallerHelper {
private static final String TAG = "CertInstallerHelper";
/* Define a password to unlock keystore after it is reset */
private static final String CERT_STORE_PASSWORD = "password";
private final int mUid = KeyStore.UID_SELF;
private PrivateKey mUserKey; // private key
private X509Certificate mUserCert; // user certificate
private List<X509Certificate> mCaCerts = new ArrayList<X509Certificate>();
private KeyStore mKeyStore = KeyStore.getInstance();
/**
* Unlock keystore and set password
*/
public CertInstallerHelper() {
mKeyStore.reset();
mKeyStore.onUserPasswordChanged(CERT_STORE_PASSWORD);
}
private void extractCertificate(String certFile, String password) {
InputStream in = null;
final byte[] raw;
java.security.KeyStore keystore = null;
try {
// Read .p12 file from SDCARD and extract with password
in = new FileInputStream(new File(
Environment.getExternalStorageDirectory(), certFile));
raw = Streams.readFully(in);
keystore = java.security.KeyStore.getInstance("PKCS12");
PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray());
keystore.load(new ByteArrayInputStream(raw), passwordProtection.getPassword());
// Install certificates and private keys
Enumeration<String> aliases = keystore.aliases();
if (!aliases.hasMoreElements()) {
Assert.fail("key store failed to put in keychain");
}
ArrayList<String> aliasesList = Collections.list(aliases);
// The keystore is initialized for each test case, there will
// be only one alias in the keystore
Assert.assertEquals(1, aliasesList.size());
String alias = aliasesList.get(0);
java.security.KeyStore.Entry entry = keystore.getEntry(alias, passwordProtection);
Log.d(TAG, "extracted alias = " + alias + ", entry=" + entry.getClass());
if (entry instanceof PrivateKeyEntry) {
Assert.assertTrue(installFrom((PrivateKeyEntry) entry));
}
} catch (IOException e) {
Assert.fail("Failed to read certficate: " + e);
} catch (KeyStoreException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (CertificateException e) {
Log.e(TAG, "failed to extract certificate" + e);
} catch (UnrecoverableEntryException e) {
Log.e(TAG, "failed to extract certificate" + e);
}
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
Log.e(TAG, "close FileInputStream error: " + e);
}
}
}
}
/**
* Extract private keys, user certificates and ca certificates
*/
private synchronized boolean installFrom(PrivateKeyEntry entry) {
mUserKey = entry.getPrivateKey();
mUserCert = (X509Certificate) entry.getCertificate();
Certificate[] certs = entry.getCertificateChain();
Log.d(TAG, "# certs extracted = " + certs.length);
mCaCerts = new ArrayList<X509Certificate>(certs.length);
for (Certificate c : certs) {
X509Certificate cert = (X509Certificate) c;
if (isCa(cert)) {
mCaCerts.add(cert);
}
}
Log.d(TAG, "# ca certs extracted = " + mCaCerts.size());
return true;
}
private boolean isCa(X509Certificate cert) {
try {
byte[] asn1EncodedBytes = cert.getExtensionValue("2.5.29.19");
if (asn1EncodedBytes == null) {
return false;
}
DEROctetString derOctetString = (DEROctetString)
new ASN1InputStream(asn1EncodedBytes).readObject();
byte[] octets = derOctetString.getOctets();
ASN1Sequence sequence = (ASN1Sequence)
new ASN1InputStream(octets).readObject();
return BasicConstraints.getInstance(sequence).isCA();
} catch (IOException e) {
return false;
}
}
/**
* Extract certificate from the given file, and install it to keystore
* @param name certificate name
* @param certFile .p12 file which includes certificates
* @param password password to extract the .p12 file
*/
public void installCertificate(VpnProfile profile, String certFile, String password) {
// extract private keys, certificates from the provided file
extractCertificate(certFile, password);
// install certificate to the keystore
int flags = KeyStore.FLAG_ENCRYPTED;
try {
if (mUserKey != null) {
Log.v(TAG, "has private key");
String key = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
byte[] value = mUserKey.getEncoded();
if (!mKeyStore.importKey(key, value, mUid, flags)) {
Log.e(TAG, "Failed to install " + key + " as user " + mUid);
return;
}
Log.v(TAG, "install " + key + " as user " + mUid + " is successful");
}
if (mUserCert != null) {
String certName = Credentials.USER_CERTIFICATE + profile.ipsecUserCert;
byte[] certData = Credentials.convertToPem(mUserCert);
if (!mKeyStore.put(certName, certData, mUid, flags)) {
Log.e(TAG, "Failed to install " + certName + " as user " + mUid);
return;
}
Log.v(TAG, "install " + certName + " as user" + mUid + " is successful.");
}
if (!mCaCerts.isEmpty()) {
String caListName = Credentials.CA_CERTIFICATE + profile.ipsecCaCert;
X509Certificate[] caCerts = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]);
byte[] caListData = Credentials.convertToPem(caCerts);
if (!mKeyStore.put(caListName, caListData, mUid, flags)) {
Log.e(TAG, "Failed to install " + caListName + " as user " + mUid);
return;
}
Log.v(TAG, " install " + caListName + " as user " + mUid + " is successful");
}
} catch (CertificateEncodingException e) {
Log.e(TAG, "Exception while convert certificates to pem " + e);
throw new AssertionError(e);
} catch (IOException e) {
Log.e(TAG, "IOException while convert to pem: " + e);
}
}
public int getUid() {
return mUid;
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2013 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.settings.vpn2;
import com.android.internal.net.VpnProfile;
/**
* Wrapper for VPN Profile and associated certificate files
*/
public class VpnInfo {
// VPN Profile
private VpnProfile mVpnProfile;
// Certificate file in PC12 format for user certificates and private keys
private String mCertificateFile = null;
// Password to extract certificates from the file
private String mPassword = null;
public VpnInfo(VpnProfile vpnProfile, String certFile, String password) {
mVpnProfile = vpnProfile;
mCertificateFile = certFile;
mPassword = password;
}
public VpnInfo(VpnProfile vpnProfile) {
mVpnProfile = vpnProfile;
}
public void setVpnProfile(VpnProfile vpnProfile) {
mVpnProfile = vpnProfile;
}
public void setCertificateFile(String certFile) {
mCertificateFile = certFile;
}
public void setPassword(String password) {
mPassword = password;
}
public VpnProfile getVpnProfile() {
return mVpnProfile;
}
public String getCertificateFile() {
return mCertificateFile;
}
public String getPassword() {
return mPassword;
}
}

View File

@@ -1,246 +0,0 @@
/*
* Copyright (C) 2013 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.settings.vpn2;
import android.util.Log;
import com.android.internal.net.VpnProfile;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Parse VPN profiles from an XML file
*/
public class VpnProfileParser {
private final static String TAG = "VpnProfileParser";
private static Map<Integer, VpnInfo> mVpnPool = new HashMap<Integer, VpnInfo>();
static DefaultHandler mHandler = new DefaultHandler() {
boolean name;
boolean type;
boolean server;
boolean username;
boolean password;
boolean dnsServers;
boolean searchDomains;
boolean routes;
boolean mppe;
boolean l2tpSecret;
boolean ipsecIdentifier;
boolean ipsecSecret;
boolean ipsecUserCert;
boolean ipsecCaCert;
boolean ipsecServerCert;
boolean certFile;
boolean certFilePassword;
VpnProfile profile = null;
VpnInfo vpnInfo = null;
@Override
public void startElement(String uri, String localName, String tagName,
Attributes attributes) throws SAXException {
if (tagName.equalsIgnoreCase("vpn")) {
//create a new VPN profile
profile = new VpnProfile(Long.toHexString(System.currentTimeMillis()));
vpnInfo = new VpnInfo(profile);
}
if (tagName.equalsIgnoreCase("name")) {
name = true;
}
if (tagName.equalsIgnoreCase("type")) {
type = true;
}
if (tagName.equalsIgnoreCase("server")) {
server = true;
}
if (tagName.equalsIgnoreCase("username")) {
username = true;
}
if (tagName.equalsIgnoreCase("password")) {
password = true;
}
if (tagName.equalsIgnoreCase("dnsServers")) {
dnsServers = true;
}
if (tagName.equalsIgnoreCase("searchDomains")) {
searchDomains = true;
}
if (tagName.equalsIgnoreCase("mppe")) {
mppe = true;
}
if (tagName.equalsIgnoreCase("l2tpSecret")) {
l2tpSecret = true;
}
if (tagName.equalsIgnoreCase("ipsecIdentifier")) {
ipsecIdentifier = true;
}
if (tagName.equalsIgnoreCase("ipsecSecret")) {
ipsecSecret = true;
}
if (tagName.equalsIgnoreCase("ipsecUserCert")) {
ipsecUserCert = true;
}
if (tagName.equalsIgnoreCase("ipsecCaCert")) {
ipsecCaCert = true;
}
if (tagName.equalsIgnoreCase("ipsecServerCert")) {
ipsecServerCert = true;
}
if (tagName.equalsIgnoreCase("routes")) {
routes = true;
}
if (tagName.equalsIgnoreCase("cert-file")) {
certFile = true;
}
if (tagName.equalsIgnoreCase("cert-file-password")) {
certFilePassword = true;
}
}
@Override
public void endElement(String uri, String localName, String tagName) throws SAXException {
if (tagName.equalsIgnoreCase("vpn")) {
mVpnPool.put(profile.type, vpnInfo);
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
String strValue = new String(ch, start, length);
if (name) {
profile.name = strValue;
name = false;
}
if (type) {
int t = getVpnProfileType(strValue);
if (t < 0) {
throw new SAXException("not a valid VPN type");
} else {
profile.type = t;
}
type = false;
}
if (server) {
profile.server = strValue;
server = false;
}
if (username) {
profile.username = strValue;
username = false;
}
if (password) {
profile.password = strValue;
password = false;
}
if (dnsServers) {
profile.dnsServers = strValue;
dnsServers = false;
}
if (searchDomains) {
profile.searchDomains = strValue;
searchDomains = false;
}
if (mppe) {
profile.mppe = Boolean.valueOf(strValue);
mppe = false;
}
if (l2tpSecret) {
profile.l2tpSecret = strValue;
l2tpSecret = false;
}
if (ipsecIdentifier) {
profile.ipsecIdentifier = strValue;
ipsecIdentifier = false;
}
if (ipsecSecret) {
profile.ipsecSecret = strValue;
ipsecSecret = false;
}
if (ipsecUserCert) {
profile.ipsecUserCert = strValue;
ipsecUserCert = false;
}
if (ipsecCaCert) {
profile.ipsecCaCert = strValue;
ipsecCaCert = false;
}
if (ipsecServerCert) {
profile.ipsecServerCert = strValue;
ipsecServerCert = false;
}
if (routes) {
profile.routes = strValue;
routes = false;
}
if (certFile) {
vpnInfo.setCertificateFile(strValue);
certFile = false;
}
if (certFilePassword) {
vpnInfo.setPassword(strValue);
certFilePassword = false;
}
}
private int getVpnProfileType(String type) {
if (type.equalsIgnoreCase("TYPE_PPTP")) {
return VpnProfile.TYPE_PPTP;
} else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_PSK")) {
return VpnProfile.TYPE_L2TP_IPSEC_PSK;
} else if (type.equalsIgnoreCase("TYPE_L2TP_IPSEC_RSA")) {
return VpnProfile.TYPE_L2TP_IPSEC_RSA;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_PSK")) {
return VpnProfile.TYPE_IPSEC_XAUTH_PSK;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_XAUTH_RSA")) {
return VpnProfile.TYPE_IPSEC_XAUTH_RSA;
} else if (type.equalsIgnoreCase("TYPE_IPSEC_HYBRID_RSA")) {
return VpnProfile.TYPE_IPSEC_HYBRID_RSA;
} else {
Log.v(TAG, "Invalid VPN type: " + type);
return -1;
}
}
};
public static Map<Integer, VpnInfo> parse(InputStream in) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
saxParser.parse(in, mHandler);
} catch (SAXException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} catch (ParserConfigurationException e) {
Log.e(TAG, "Parse vpn profile exception: " + e.toString());
} finally {
return mVpnPool;
}
}
}

View File

@@ -1,446 +0,0 @@
/*
* Copyright (C) 2013 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.settings.vpn2;
import android.content.Context;
import android.net.IConnectivityManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.security.Credentials;
import android.security.KeyStore;
import android.security.NetworkSecurityPolicy;
import android.test.InstrumentationTestCase;
import android.test.InstrumentationTestRunner;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import java.net.HttpURLConnection;
import java.net.URL;
import junit.framework.Assert;
import libcore.io.Streams;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* Legacy VPN connection tests
*
* To run the test, use command:
* adb shell am instrument -e class com.android.settings.vpn2.VpnTests -e profile foo.xml
* -w com.android.settings.tests/android.test.InstrumentationTestRunner
*
* VPN profiles are saved in an xml file and will be loaded through {@link VpnProfileParser}.
* Push the profile (foo.xml) to the external storage, e.g adb push foo.xml /sdcard/ before running
* the above command.
*
* A typical profile looks like the following:
* <vpn>
* <name></name>
* <type></type>
* <server></server>
* <username></username>
* <password></password>
* <dnsServers></dnsServers>
* <searchDomains></searchDomains>
* <routes></routes>
* <l2tpSecret></l2tpSecret>
* <ipsecIdentifier></ipsecIdentifier>
* <ipsecSecret></ipsecSecret>
* <ipsecUserCert></ipsecUserCert>
* <ipsecCaCert></ipsecCaCert>
* <ipsecServerCert></ipsecServerCert>
* </vpn>
* VPN types include: TYPE_PPTP, TYPE_L2TP_IPSEC_PSK, TYPE_L2TP_IPSEC_RSA,
* TYPE_IPSEC_XAUTH_PSK, TYPE_IPSEC_XAUTH_RSA, TYPE_IPSEC_HYBRID_RSA
*/
public class VpnTests extends InstrumentationTestCase {
private static final String TAG = "VpnTests";
/* Maximum time to wait for VPN connection */
private static final long MAX_CONNECTION_TIME = 5 * 60 * 1000;
private static final long VPN_STAY_TIME = 60 * 1000;
private static final int MAX_DISCONNECTION_TRIES = 3;
private static final String EXTERNAL_SERVER =
"http://ip2country.sourceforge.net/ip2c.php?format=JSON";
private static final String VPN_INTERFACE = "ppp0";
private final IConnectivityManager mService = IConnectivityManager.Stub
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private Map<Integer, VpnInfo> mVpnInfoPool = null;
private Context mContext;
private CertInstallerHelper mCertHelper = null;
private KeyStore mKeyStore = KeyStore.getInstance();
private String mPreviousIpAddress = null;
private boolean DEBUG = false;
@Override
protected void setUp() throws Exception {
super.setUp();
InputStream in = null;
InstrumentationTestRunner mRunner = (InstrumentationTestRunner)getInstrumentation();
mContext = mRunner.getContext();
Bundle arguments = mRunner.getArguments();
String PROFILE_NAME = arguments.getString("profile");
Assert.assertNotNull("Push profile to external storage and load with"
+ "'-e profile <filename>'", PROFILE_NAME);
File profileFile = new File(Environment.getExternalStorageDirectory(), PROFILE_NAME);
in = new FileInputStream(profileFile);
mVpnInfoPool = VpnProfileParser.parse(in);
Assert.assertNotNull("no VPN profiles are parsed", mVpnInfoPool);
if (DEBUG) {
Log.v(TAG, "print out the vpn profiles");
for (Map.Entry<Integer, VpnInfo> profileEntrySet: mVpnInfoPool.entrySet()) {
VpnInfo vpnInfo = profileEntrySet.getValue();
printVpnProfile(vpnInfo.getVpnProfile());
if (vpnInfo.getCertificateFile() != null) {
Log.d(TAG, "certificate file for this vpn is " + vpnInfo.getCertificateFile());
}
if (vpnInfo.getPassword() != null) {
Log.d(TAG, "password for the certificate file is: " + vpnInfo.getPassword());
}
}
}
// disconnect existing vpn if there is any
LegacyVpnInfo oldVpn = mService.getLegacyVpnInfo(UserHandle.myUserId());
if (oldVpn != null) {
Log.v(TAG, "disconnect legacy VPN");
disconnect();
// wait till the legacy VPN is disconnected.
int tries = 0;
while (tries < MAX_DISCONNECTION_TRIES &&
mService.getLegacyVpnInfo(UserHandle.myUserId()) != null) {
tries++;
Thread.sleep(10 * 1000);
Log.v(TAG, "Wait for legacy VPN to be disconnected.");
}
Assert.assertNull("Failed to disconect VPN",
mService.getLegacyVpnInfo(UserHandle.myUserId()));
// wait for 30 seconds after the previous VPN is disconnected.
sleep(30 * 1000);
}
// Create CertInstallerHelper to initialize the keystore
mCertHelper = new CertInstallerHelper();
}
@Override
protected void tearDown() throws Exception {
sleep(VPN_STAY_TIME);
super.tearDown();
}
private void printVpnProfile(VpnProfile profile) {
Log.v(TAG, "profile: ");
Log.v(TAG, "key: " + profile.key);
Log.v(TAG, "name: " + profile.name);
Log.v(TAG, "type: " + profile.type);
Log.v(TAG, "server: " + profile.server);
Log.v(TAG, "username: " + profile.username);
Log.v(TAG, "password: " + profile.password);
Log.v(TAG, "dnsServers: " + profile.dnsServers);
Log.v(TAG, "searchDomains: " + profile.searchDomains);
Log.v(TAG, "routes: " + profile.routes);
Log.v(TAG, "mppe: " + profile.mppe);
Log.v(TAG, "l2tpSecret: " + profile.l2tpSecret);
Log.v(TAG, "ipsecIdentifier: " + profile.ipsecIdentifier);
Log.v(TAG, "ipsecSecret: " + profile.ipsecSecret);
Log.v(TAG, "ipsecUserCert: " + profile.ipsecUserCert);
Log.v(TAG, "ipsecCaCert: " + profile.ipsecCaCert);
Log.v(TAG, "ipsecServerCert: " + profile.ipsecServerCert);
}
private void printKeyStore(VpnProfile profile) {
// print out the information from keystore
String privateKey = "";
String userCert = "";
String caCert = "";
String serverCert = "";
if (!profile.ipsecUserCert.isEmpty()) {
privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
if (!profile.ipsecCaCert.isEmpty()) {
byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
if (!profile.ipsecServerCert.isEmpty()) {
byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
}
Log.v(TAG, "privateKey: \n" + ((privateKey == null) ? "" : privateKey));
Log.v(TAG, "userCert: \n" + ((userCert == null) ? "" : userCert));
Log.v(TAG, "caCert: \n" + ((caCert == null) ? "" : caCert));
Log.v(TAG, "serverCert: \n" + ((serverCert == null) ? "" : serverCert));
}
/**
* Connect legacy VPN
*/
private void connect(VpnProfile profile) throws Exception {
try {
mService.startLegacyVpn(profile);
} catch (IllegalStateException e) {
fail(String.format("start legacy vpn: %s failed: %s", profile.name, e.toString()));
}
}
/**
* Disconnect legacy VPN
*/
private void disconnect() throws Exception {
try {
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, UserHandle.myUserId());
} catch (RemoteException e) {
Log.e(TAG, String.format("disconnect VPN exception: %s", e.toString()));
}
}
/**
* Get external IP address
*/
private String getIpAddress() {
String ip = null;
HttpURLConnection urlConnection = null;
// TODO: Rewrite this test to use an HTTPS URL.
// Because this test uses cleartext HTTP, the network security policy of this app needs to
// be temporarily relaxed to permit such traffic.
NetworkSecurityPolicy networkSecurityPolicy = NetworkSecurityPolicy.getInstance();
boolean cleartextTrafficPermittedBeforeTest =
networkSecurityPolicy.isCleartextTrafficPermitted();
networkSecurityPolicy.setCleartextTrafficPermitted(true);
try {
URL url = new URL(EXTERNAL_SERVER);
urlConnection = (HttpURLConnection) url.openConnection();
Log.i(TAG, "Response from httpget: " + urlConnection.getResponseCode());
InputStream is = urlConnection.getInputStream();
String response;
try {
response = new String(Streams.readFully(is), StandardCharsets.UTF_8);
} finally {
is.close();
}
JSONObject json_data = new JSONObject(response);
ip = json_data.getString("ip");
Log.v(TAG, "json_data: " + ip);
} catch (IllegalArgumentException e) {
Log.e(TAG, "exception while getting external IP: " + e.toString());
} catch (IOException e) {
Log.e(TAG, "IOException while getting IP: " + e.toString());
} catch (JSONException e) {
Log.e(TAG, "exception while creating JSONObject: " + e.toString());
} finally {
networkSecurityPolicy.setCleartextTrafficPermitted(cleartextTrafficPermittedBeforeTest);
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return ip;
}
/**
* Verify the vpn connection by checking the VPN state and external IP
*/
private void validateVpnConnection(VpnProfile profile) throws Exception {
validateVpnConnection(profile, false);
}
/**
* Verify the vpn connection by checking the VPN state, external IP or ping test
*/
private void validateVpnConnection(VpnProfile profile, boolean pingTestFlag) throws Exception {
LegacyVpnInfo legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
Assert.assertTrue(legacyVpnInfo != null);
long start = System.currentTimeMillis();
while (((System.currentTimeMillis() - start) < MAX_CONNECTION_TIME) &&
(legacyVpnInfo.state != LegacyVpnInfo.STATE_CONNECTED)) {
Log.v(TAG, "vpn state: " + legacyVpnInfo.state);
sleep(10 * 1000);
legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
}
// the vpn state should be CONNECTED
Assert.assertTrue(legacyVpnInfo.state == LegacyVpnInfo.STATE_CONNECTED);
if (pingTestFlag) {
Assert.assertTrue(pingTest(profile.server));
} else {
String curIpAddress = getIpAddress();
// the outgoing IP address should be the same as the VPN server address
Assert.assertEquals(profile.server, curIpAddress);
}
}
private boolean pingTest(String server) {
final long PING_TIMER = 3 * 60 * 1000; // 3 minutes
if (server == null || server.isEmpty()) {
return false;
}
long startTime = System.currentTimeMillis();
while ((System.currentTimeMillis() - startTime) < PING_TIMER) {
try {
Log.v(TAG, "Start ping test, ping " + server);
Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + server);
int status = p.waitFor();
if (status == 0) {
// if any of the ping test is successful, return true
return true;
}
} catch (UnknownHostException e) {
Log.e(TAG, "Ping test Fail: Unknown Host");
} catch (IOException e) {
Log.e(TAG, "Ping test Fail: IOException");
} catch (InterruptedException e) {
Log.e(TAG, "Ping test Fail: InterruptedException");
}
}
// ping test timeout
return false;
}
/**
* Install certificates from a file loaded in external stroage on the device
* @param profile vpn profile
* @param fileName certificate file name
* @param password password to extract certificate file
*/
private void installCertificatesFromFile(VpnProfile profile, String fileName, String password)
throws Exception {
if (profile == null || fileName == null || password == null) {
throw new Exception ("vpn profile, certificate file name and password can not be null");
}
int curUid = mContext.getUserId();
mCertHelper.installCertificate(profile, fileName, password);
if (DEBUG) {
printKeyStore(profile);
}
}
private void sleep(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
Log.e(TAG, "interrupted: " + e.toString());
}
}
/**
* Test PPTP VPN connection
*/
@LargeTest
public void testPPTPConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_PPTP);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test L2TP/IPSec PSK VPN connection
*/
@LargeTest
public void testL2tpIpsecPskConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_PSK);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test L2TP/IPSec RSA VPN connection
*/
@LargeTest
public void testL2tpIpsecRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
String certFile = curVpnInfo.getCertificateFile();
String password = curVpnInfo.getPassword();
installCertificatesFromFile(vpnProfile, certFile, password);
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test IPSec Xauth RSA VPN connection
*/
@LargeTest
public void testIpsecXauthRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
String certFile = curVpnInfo.getCertificateFile();
String password = curVpnInfo.getPassword();
installCertificatesFromFile(vpnProfile, certFile, password);
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
/**
* Test IPSec Xauth PSK VPN connection
*/
@LargeTest
public void testIpsecXauthPskConnection() throws Exception {
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_PSK);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
connect(vpnProfile);
validateVpnConnection(vpnProfile, true);
}
/**
* Test IPSec Hybrid RSA VPN connection
*/
@LargeTest
public void testIpsecHybridRsaConnection() throws Exception {
mPreviousIpAddress = getIpAddress();
VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_HYBRID_RSA);
VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
if (DEBUG) {
printVpnProfile(vpnProfile);
}
connect(vpnProfile);
validateVpnConnection(vpnProfile);
}
}