Home | History | Annotate | Download | only in vpn2
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.vpn2;
     18 
     19 import android.content.Context;
     20 import android.net.IConnectivityManager;
     21 import android.os.Bundle;
     22 import android.os.Environment;
     23 import android.os.RemoteException;
     24 import android.os.ServiceManager;
     25 import android.os.UserHandle;
     26 import android.security.Credentials;
     27 import android.security.KeyStore;
     28 import android.security.NetworkSecurityPolicy;
     29 import android.test.InstrumentationTestCase;
     30 import android.test.InstrumentationTestRunner;
     31 import android.test.suitebuilder.annotation.LargeTest;
     32 import android.util.Log;
     33 
     34 import com.android.internal.net.LegacyVpnInfo;
     35 import com.android.internal.net.VpnConfig;
     36 import com.android.internal.net.VpnProfile;
     37 
     38 import java.net.HttpURLConnection;
     39 import java.net.URL;
     40 import junit.framework.Assert;
     41 
     42 import libcore.io.Streams;
     43 import org.json.JSONException;
     44 import org.json.JSONObject;
     45 
     46 import java.io.File;
     47 import java.io.FileInputStream;
     48 import java.io.IOException;
     49 import java.io.InputStream;
     50 import java.net.UnknownHostException;
     51 import java.nio.charset.StandardCharsets;
     52 import java.util.List;
     53 import java.util.Map;
     54 
     55 /**
     56  * Legacy VPN connection tests
     57  *
     58  * To run the test, use command:
     59  * adb shell am instrument -e class com.android.settings.vpn2.VpnTests -e profile foo.xml
     60  * -w com.android.settings.tests/android.test.InstrumentationTestRunner
     61  *
     62  * VPN profiles are saved in an xml file and will be loaded through {@link VpnProfileParser}.
     63  * Push the profile (foo.xml) to the external storage, e.g adb push foo.xml /sdcard/ before running
     64  * the above command.
     65  *
     66  * A typical profile looks like the following:
     67  * <vpn>
     68  *   <name></name>
     69  *   <type></type>
     70  *   <server></server>
     71  *   <username></username>
     72  *   <password></password>
     73  *   <dnsServers></dnsServers>
     74  *   <searchDomains></searchDomains>
     75  *   <routes></routes>
     76  *   <l2tpSecret></l2tpSecret>
     77  *   <ipsecIdentifier></ipsecIdentifier>
     78  *   <ipsecSecret></ipsecSecret>
     79  *   <ipsecUserCert></ipsecUserCert>
     80  *   <ipsecCaCert></ipsecCaCert>
     81  *   <ipsecServerCert></ipsecServerCert>
     82  * </vpn>
     83  * VPN types include: TYPE_PPTP, TYPE_L2TP_IPSEC_PSK, TYPE_L2TP_IPSEC_RSA,
     84  * TYPE_IPSEC_XAUTH_PSK, TYPE_IPSEC_XAUTH_RSA, TYPE_IPSEC_HYBRID_RSA
     85  */
     86 public class VpnTests extends InstrumentationTestCase {
     87     private static final String TAG = "VpnTests";
     88     /* Maximum time to wait for VPN connection */
     89     private static final long MAX_CONNECTION_TIME = 5 * 60 * 1000;
     90     private static final long VPN_STAY_TIME = 60 * 1000;
     91     private static final int MAX_DISCONNECTION_TRIES = 3;
     92     private static final String EXTERNAL_SERVER =
     93             "http://ip2country.sourceforge.net/ip2c.php?format=JSON";
     94     private static final String VPN_INTERFACE = "ppp0";
     95     private final IConnectivityManager mService = IConnectivityManager.Stub
     96         .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
     97     private Map<Integer, VpnInfo> mVpnInfoPool = null;
     98     private Context mContext;
     99     private CertInstallerHelper mCertHelper = null;
    100     private KeyStore mKeyStore = KeyStore.getInstance();
    101     private String mPreviousIpAddress = null;
    102     private boolean DEBUG = false;
    103 
    104     @Override
    105     protected void setUp() throws Exception {
    106         super.setUp();
    107         InputStream in = null;
    108         InstrumentationTestRunner mRunner = (InstrumentationTestRunner)getInstrumentation();
    109         mContext = mRunner.getContext();
    110         Bundle arguments = mRunner.getArguments();
    111         String PROFILE_NAME = arguments.getString("profile");
    112         Assert.assertNotNull("Push profile to external storage and load with"
    113                 + "'-e profile <filename>'", PROFILE_NAME);
    114         File profileFile = new File(Environment.getExternalStorageDirectory(), PROFILE_NAME);
    115         in = new FileInputStream(profileFile);
    116         mVpnInfoPool = VpnProfileParser.parse(in);
    117         Assert.assertNotNull("no VPN profiles are parsed", mVpnInfoPool);
    118         if (DEBUG) {
    119             Log.v(TAG, "print out the vpn profiles");
    120             for (Map.Entry<Integer, VpnInfo> profileEntrySet: mVpnInfoPool.entrySet()) {
    121                 VpnInfo vpnInfo = profileEntrySet.getValue();
    122                 printVpnProfile(vpnInfo.getVpnProfile());
    123                 if (vpnInfo.getCertificateFile() != null) {
    124                     Log.d(TAG, "certificate file for this vpn is " + vpnInfo.getCertificateFile());
    125                 }
    126                 if (vpnInfo.getPassword() != null) {
    127                     Log.d(TAG, "password for the certificate file is: " + vpnInfo.getPassword());
    128                 }
    129             }
    130         }
    131         // disconnect existing vpn if there is any
    132         LegacyVpnInfo oldVpn = mService.getLegacyVpnInfo(UserHandle.myUserId());
    133         if (oldVpn != null) {
    134             Log.v(TAG, "disconnect legacy VPN");
    135             disconnect();
    136             // wait till the legacy VPN is disconnected.
    137             int tries = 0;
    138             while (tries < MAX_DISCONNECTION_TRIES &&
    139                     mService.getLegacyVpnInfo(UserHandle.myUserId()) != null) {
    140                 tries++;
    141                 Thread.sleep(10 * 1000);
    142                 Log.v(TAG, "Wait for legacy VPN to be disconnected.");
    143             }
    144             Assert.assertNull("Failed to disconect VPN",
    145                     mService.getLegacyVpnInfo(UserHandle.myUserId()));
    146             // wait for 30 seconds after the previous VPN is disconnected.
    147             sleep(30 * 1000);
    148         }
    149         // Create CertInstallerHelper to initialize the keystore
    150         mCertHelper = new CertInstallerHelper();
    151     }
    152 
    153     @Override
    154     protected void tearDown() throws Exception {
    155         sleep(VPN_STAY_TIME);
    156         super.tearDown();
    157     }
    158 
    159     private void printVpnProfile(VpnProfile profile) {
    160         Log.v(TAG, "profile: ");
    161         Log.v(TAG, "key: " + profile.key);
    162         Log.v(TAG, "name: " + profile.name);
    163         Log.v(TAG, "type: " + profile.type);
    164         Log.v(TAG, "server: " + profile.server);
    165         Log.v(TAG, "username: " + profile.username);
    166         Log.v(TAG, "password: " + profile.password);
    167         Log.v(TAG, "dnsServers: " + profile.dnsServers);
    168         Log.v(TAG, "searchDomains: " + profile.searchDomains);
    169         Log.v(TAG, "routes: " + profile.routes);
    170         Log.v(TAG, "mppe: " + profile.mppe);
    171         Log.v(TAG, "l2tpSecret: " + profile.l2tpSecret);
    172         Log.v(TAG, "ipsecIdentifier: " + profile.ipsecIdentifier);
    173         Log.v(TAG, "ipsecSecret: " + profile.ipsecSecret);
    174         Log.v(TAG, "ipsecUserCert: " + profile.ipsecUserCert);
    175         Log.v(TAG, "ipsecCaCert: " + profile.ipsecCaCert);
    176         Log.v(TAG, "ipsecServerCert: " + profile.ipsecServerCert);
    177     }
    178 
    179     private void printKeyStore(VpnProfile profile) {
    180         // print out the information from keystore
    181         String privateKey = "";
    182         String userCert = "";
    183         String caCert = "";
    184         String serverCert = "";
    185         if (!profile.ipsecUserCert.isEmpty()) {
    186             privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
    187             byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
    188             userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
    189         }
    190         if (!profile.ipsecCaCert.isEmpty()) {
    191             byte[] value = mKeyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
    192             caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
    193         }
    194         if (!profile.ipsecServerCert.isEmpty()) {
    195             byte[] value = mKeyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
    196             serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
    197         }
    198         Log.v(TAG, "privateKey: \n" + ((privateKey == null) ? "" : privateKey));
    199         Log.v(TAG, "userCert: \n" + ((userCert == null) ? "" : userCert));
    200         Log.v(TAG, "caCert: \n" + ((caCert == null) ? "" : caCert));
    201         Log.v(TAG, "serverCert: \n" + ((serverCert == null) ? "" : serverCert));
    202     }
    203 
    204     /**
    205      * Connect legacy VPN
    206      */
    207     private void connect(VpnProfile profile) throws Exception {
    208         try {
    209             mService.startLegacyVpn(profile);
    210         } catch (IllegalStateException e) {
    211             fail(String.format("start legacy vpn: %s failed: %s", profile.name, e.toString()));
    212         }
    213     }
    214 
    215     /**
    216      * Disconnect legacy VPN
    217      */
    218     private void disconnect() throws Exception {
    219         try {
    220             mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, UserHandle.myUserId());
    221         } catch (RemoteException e) {
    222             Log.e(TAG, String.format("disconnect VPN exception: %s", e.toString()));
    223         }
    224     }
    225 
    226     /**
    227      * Get external IP address
    228      */
    229     private String getIpAddress() {
    230         String ip = null;
    231         HttpURLConnection urlConnection = null;
    232         // TODO: Rewrite this test to use an HTTPS URL.
    233         // Because this test uses cleartext HTTP, the network security policy of this app needs to
    234         // be temporarily relaxed to permit such traffic.
    235         NetworkSecurityPolicy networkSecurityPolicy = NetworkSecurityPolicy.getInstance();
    236         boolean cleartextTrafficPermittedBeforeTest =
    237                 networkSecurityPolicy.isCleartextTrafficPermitted();
    238         networkSecurityPolicy.setCleartextTrafficPermitted(true);
    239         try {
    240             URL url = new URL(EXTERNAL_SERVER);
    241             urlConnection = (HttpURLConnection) url.openConnection();
    242             Log.i(TAG, "Response from httpget: " + urlConnection.getResponseCode());
    243 
    244             InputStream is = urlConnection.getInputStream();
    245             String response;
    246             try {
    247                 response = new String(Streams.readFully(is), StandardCharsets.UTF_8);
    248             } finally {
    249                 is.close();
    250             }
    251 
    252             JSONObject json_data = new JSONObject(response);
    253             ip = json_data.getString("ip");
    254             Log.v(TAG, "json_data: " + ip);
    255         } catch (IllegalArgumentException e) {
    256             Log.e(TAG, "exception while getting external IP: " + e.toString());
    257         } catch (IOException e) {
    258             Log.e(TAG, "IOException while getting IP: " + e.toString());
    259         } catch (JSONException e) {
    260             Log.e(TAG, "exception while creating JSONObject: " + e.toString());
    261         } finally {
    262             networkSecurityPolicy.setCleartextTrafficPermitted(cleartextTrafficPermittedBeforeTest);
    263             if (urlConnection != null) {
    264                 urlConnection.disconnect();
    265             }
    266         }
    267         return ip;
    268     }
    269 
    270     /**
    271      * Verify the vpn connection by checking the VPN state and external IP
    272      */
    273     private void validateVpnConnection(VpnProfile profile) throws Exception {
    274         validateVpnConnection(profile, false);
    275     }
    276 
    277     /**
    278      * Verify the vpn connection by checking the VPN state, external IP or ping test
    279      */
    280     private void validateVpnConnection(VpnProfile profile, boolean pingTestFlag) throws Exception {
    281         LegacyVpnInfo legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
    282         Assert.assertTrue(legacyVpnInfo != null);
    283 
    284         long start = System.currentTimeMillis();
    285         while (((System.currentTimeMillis() - start)  < MAX_CONNECTION_TIME) &&
    286                 (legacyVpnInfo.state != LegacyVpnInfo.STATE_CONNECTED)) {
    287             Log.v(TAG, "vpn state: " + legacyVpnInfo.state);
    288             sleep(10 * 1000);
    289             legacyVpnInfo = mService.getLegacyVpnInfo(UserHandle.myUserId());
    290         }
    291 
    292         // the vpn state should be CONNECTED
    293         Assert.assertTrue(legacyVpnInfo.state == LegacyVpnInfo.STATE_CONNECTED);
    294         if (pingTestFlag) {
    295             Assert.assertTrue(pingTest(profile.server));
    296         } else {
    297             String curIpAddress = getIpAddress();
    298             // the outgoing IP address should be the same as the VPN server address
    299             Assert.assertEquals(profile.server, curIpAddress);
    300         }
    301     }
    302 
    303     private boolean pingTest(String server) {
    304         final long PING_TIMER = 3 * 60 * 1000; // 3 minutes
    305         if (server == null || server.isEmpty()) {
    306             return false;
    307         }
    308         long startTime = System.currentTimeMillis();
    309         while ((System.currentTimeMillis() - startTime) < PING_TIMER) {
    310             try {
    311                 Log.v(TAG, "Start ping test, ping " + server);
    312                 Process p = Runtime.getRuntime().exec("ping -c 10 -w 100 " + server);
    313                 int status = p.waitFor();
    314                 if (status == 0) {
    315                     // if any of the ping test is successful, return true
    316                     return true;
    317                 }
    318             } catch (UnknownHostException e) {
    319                 Log.e(TAG, "Ping test Fail: Unknown Host");
    320             } catch (IOException e) {
    321                 Log.e(TAG, "Ping test Fail:  IOException");
    322             } catch (InterruptedException e) {
    323                 Log.e(TAG, "Ping test Fail: InterruptedException");
    324             }
    325         }
    326         // ping test timeout
    327         return false;
    328     }
    329 
    330     /**
    331      * Install certificates from a file loaded in external stroage on the device
    332      * @param profile vpn profile
    333      * @param fileName certificate file name
    334      * @param password password to extract certificate file
    335      */
    336     private void installCertificatesFromFile(VpnProfile profile, String fileName, String password)
    337             throws Exception {
    338         if (profile == null || fileName == null || password == null) {
    339             throw new Exception ("vpn profile, certificate file name and password can not be null");
    340         }
    341 
    342         int curUid = mContext.getUserId();
    343         mCertHelper.installCertificate(profile, fileName, password);
    344 
    345         if (DEBUG) {
    346             printKeyStore(profile);
    347         }
    348     }
    349 
    350     private void sleep(long time) {
    351         try {
    352             Thread.sleep(time);
    353         } catch (InterruptedException e) {
    354             Log.e(TAG, "interrupted: " + e.toString());
    355         }
    356     }
    357 
    358     /**
    359      * Test PPTP VPN connection
    360      */
    361     @LargeTest
    362     public void testPPTPConnection() throws Exception {
    363         mPreviousIpAddress = getIpAddress();
    364         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_PPTP);
    365         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    366         connect(vpnProfile);
    367         validateVpnConnection(vpnProfile);
    368     }
    369 
    370     /**
    371      * Test L2TP/IPSec PSK VPN connection
    372      */
    373     @LargeTest
    374     public void testL2tpIpsecPskConnection() throws Exception {
    375         mPreviousIpAddress = getIpAddress();
    376         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_PSK);
    377         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    378         connect(vpnProfile);
    379         validateVpnConnection(vpnProfile);
    380     }
    381 
    382     /**
    383      * Test L2TP/IPSec RSA VPN connection
    384      */
    385     @LargeTest
    386     public void testL2tpIpsecRsaConnection() throws Exception {
    387         mPreviousIpAddress = getIpAddress();
    388         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_L2TP_IPSEC_RSA);
    389         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    390         if (DEBUG) {
    391             printVpnProfile(vpnProfile);
    392         }
    393         String certFile = curVpnInfo.getCertificateFile();
    394         String password = curVpnInfo.getPassword();
    395         installCertificatesFromFile(vpnProfile, certFile, password);
    396         connect(vpnProfile);
    397         validateVpnConnection(vpnProfile);
    398     }
    399 
    400     /**
    401      * Test IPSec Xauth RSA VPN connection
    402      */
    403     @LargeTest
    404     public void testIpsecXauthRsaConnection() throws Exception {
    405         mPreviousIpAddress = getIpAddress();
    406         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_RSA);
    407         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    408         if (DEBUG) {
    409             printVpnProfile(vpnProfile);
    410         }
    411         String certFile = curVpnInfo.getCertificateFile();
    412         String password = curVpnInfo.getPassword();
    413         installCertificatesFromFile(vpnProfile, certFile, password);
    414         connect(vpnProfile);
    415         validateVpnConnection(vpnProfile);
    416     }
    417 
    418     /**
    419      * Test IPSec Xauth PSK VPN connection
    420      */
    421     @LargeTest
    422     public void testIpsecXauthPskConnection() throws Exception {
    423         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_XAUTH_PSK);
    424         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    425         if (DEBUG) {
    426             printVpnProfile(vpnProfile);
    427         }
    428         connect(vpnProfile);
    429         validateVpnConnection(vpnProfile, true);
    430     }
    431 
    432     /**
    433      * Test IPSec Hybrid RSA VPN connection
    434      */
    435     @LargeTest
    436     public void testIpsecHybridRsaConnection() throws Exception {
    437         mPreviousIpAddress = getIpAddress();
    438         VpnInfo curVpnInfo = mVpnInfoPool.get(VpnProfile.TYPE_IPSEC_HYBRID_RSA);
    439         VpnProfile vpnProfile = curVpnInfo.getVpnProfile();
    440         if (DEBUG) {
    441             printVpnProfile(vpnProfile);
    442         }
    443         connect(vpnProfile);
    444         validateVpnConnection(vpnProfile);
    445     }
    446 }
    447