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