OSDN Git Service

Add legacy VPN test framework and test cases
authorXia Wang <xiaw@google.com>
Sat, 29 Mar 2014 00:20:53 +0000 (17:20 -0700)
committerXia Wang <xiaw@google.com>
Wed, 2 Apr 2014 00:45:58 +0000 (17:45 -0700)
- VpnProfileParser can parse VPN profiles from an xml file
- CertInstallerHelper installs keys and certificates to keystore
- VpnTests includes all test cases

Change-Id: Ib5ce33e770ee4f82bea153f9b5c5cf3802f95b0c

tests/Android.mk
tests/AndroidManifest.xml
tests/src/com/android/settings/vpn2/CertInstallerHelper.java [new file with mode: 0644]
tests/src/com/android/settings/vpn2/VpnInfo.java [new file with mode: 0644]
tests/src/com/android/settings/vpn2/VpnProfileParser.java [new file with mode: 0644]
tests/src/com/android/settings/vpn2/VpnTests.java [new file with mode: 0644]

index f54aeee..bb31539 100644 (file)
@@ -5,7 +5,7 @@ include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := tests
 LOCAL_CERTIFICATE := platform
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
index 53bf40f..aa7f947 100644 (file)
@@ -19,6 +19,9 @@
 
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/src/com/android/settings/vpn2/CertInstallerHelper.java b/tests/src/com/android/settings/vpn2/CertInstallerHelper.java
new file mode 100644 (file)
index 0000000..f95893f
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * 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.password(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;
+    }
+}
diff --git a/tests/src/com/android/settings/vpn2/VpnInfo.java b/tests/src/com/android/settings/vpn2/VpnInfo.java
new file mode 100644 (file)
index 0000000..ab7fb0f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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;
+    }
+}
diff --git a/tests/src/com/android/settings/vpn2/VpnProfileParser.java b/tests/src/com/android/settings/vpn2/VpnProfileParser.java
new file mode 100644 (file)
index 0000000..51c2550
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * 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;
+        }
+    }
+}
diff --git a/tests/src/com/android/settings/vpn2/VpnTests.java b/tests/src/com/android/settings/vpn2/VpnTests.java
new file mode 100644 (file)
index 0000000..2c6963a
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+ * 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.net.LinkAddress;
+import android.net.RouteInfo;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+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 junit.framework.Assert;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+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 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();
+        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() != null) {
+                tries++;
+                Thread.sleep(10 * 1000);
+                Log.v(TAG, "Wait for legacy VPN to be disconnected.");
+            }
+            Assert.assertNull("Failed to disconect VPN", mService.getLegacyVpnInfo());
+            // wait for 30 seconds after the previous VPN is disconnected.
+            sleep(30 * 1000);
+        }
+        // Create CertInstallerHelper to initialize the keystore
+        mCertHelper = new CertInstallerHelper();
+    }
+
+    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);
+        } catch (RemoteException e) {
+            Log.e(TAG, String.format("disconnect VPN exception: %s", e.toString()));
+        }
+    }
+
+    /**
+     * Get external IP address
+     */
+    private String getIpAddress() {
+        String ip = null;
+        try {
+            HttpClient httpClient = new DefaultHttpClient();
+            HttpGet httpGet = new HttpGet(EXTERNAL_SERVER);
+            HttpResponse httpResponse = httpClient.execute(httpGet);
+            Log.i(TAG, "Response from httpget: " + httpResponse.getStatusLine().toString());
+
+            String entityStr = EntityUtils.toString(httpResponse.getEntity());
+            JSONObject json_data = new JSONObject(entityStr);
+            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());
+        }
+        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();
+        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();
+        }
+
+        // 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);
+    }
+}