1 /* 2 * Copyright (C) 2016 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.server.wifi; 18 19 import android.net.IpConfiguration; 20 import android.net.wifi.WifiConfiguration; 21 import android.net.wifi.WifiEnterpriseConfig; 22 import android.os.Process; 23 import android.util.Log; 24 import android.util.SparseArray; 25 import android.util.Xml; 26 27 import com.android.internal.util.FastXmlSerializer; 28 import com.android.server.net.IpConfigStore; 29 import com.android.server.wifi.util.NativeUtil; 30 import com.android.server.wifi.util.WifiPermissionsUtil; 31 import com.android.server.wifi.util.XmlUtil; 32 import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil; 33 import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil; 34 35 import org.xmlpull.v1.XmlPullParser; 36 import org.xmlpull.v1.XmlPullParserException; 37 import org.xmlpull.v1.XmlSerializer; 38 39 import java.io.BufferedReader; 40 import java.io.ByteArrayInputStream; 41 import java.io.ByteArrayOutputStream; 42 import java.io.CharArrayReader; 43 import java.io.FileDescriptor; 44 import java.io.IOException; 45 import java.io.PrintWriter; 46 import java.io.UnsupportedEncodingException; 47 import java.nio.charset.StandardCharsets; 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 52 /** 53 * Class used to backup/restore data using the SettingsBackupAgent. 54 * There are 2 symmetric API's exposed here: 55 * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up. 56 * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data. 57 * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across 58 * revisions. 59 */ 60 public class WifiBackupRestore { 61 private static final String TAG = "WifiBackupRestore"; 62 63 /** 64 * Current backup data version. 65 * Note: before Android P this used to be an {@code int}, however support for minor versions 66 * has been added in Android P. Currently this field is a {@code float} representing 67 * "majorVersion.minorVersion" of the backed up data. MinorVersion starts with 0 and should 68 * be incremented when necessary. MajorVersion starts with 1 and bumping it up requires 69 * also resetting minorVersion to 0. 70 * 71 * MajorVersion will be incremented for modifications of the XML schema, excluding additive 72 * modifications in <WifiConfiguration> and/or <IpConfiguration> tags. 73 * Should the major version be bumped up, a new {@link WifiBackupDataParser} parser needs to 74 * be added and returned from {@link #getWifiBackupDataParser(int)} ()}. 75 * Note that bumping up the major version will result in inability to restore the backup 76 * set to those lower versions of SDK_INT that don't support the version. 77 * 78 * MinorVersion will only be incremented for addition of <WifiConfiguration> and/or 79 * <IpConfiguration> tags. Any other modifications to the schema should result in bumping up 80 * the major version and resetting the minor version to 0. 81 * Note that bumping up only the minor version will still allow restoring the backup set to 82 * lower versions of SDK_INT. 83 */ 84 private static final float CURRENT_BACKUP_DATA_VERSION = 1.1f; 85 86 /** This list of older versions will be used to restore data from older backups. */ 87 /** 88 * First version of the backup data format. 89 */ 90 private static final int INITIAL_BACKUP_DATA_VERSION = 1; 91 92 /** 93 * List of XML section header tags in the backed up data 94 */ 95 private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData"; 96 private static final String XML_TAG_VERSION = "Version"; 97 98 static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList"; 99 static final String XML_TAG_SECTION_HEADER_NETWORK = "Network"; 100 static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration"; 101 static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration"; 102 103 /** 104 * Regex to mask out passwords in backup data dump. 105 */ 106 private static final String PSK_MASK_LINE_MATCH_PATTERN = 107 "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>"; 108 private static final String PSK_MASK_SEARCH_PATTERN = 109 "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)"; 110 private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3"; 111 112 private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN = 113 "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">"; 114 private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>"; 115 private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)"; 116 private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3"; 117 118 private final WifiPermissionsUtil mWifiPermissionsUtil; 119 /** 120 * Verbose logging flag. 121 */ 122 private boolean mVerboseLoggingEnabled = false; 123 124 /** 125 * Store the dump of the backup/restore data for debugging. This is only stored when verbose 126 * logging is enabled in developer options. 127 */ 128 private byte[] mDebugLastBackupDataRetrieved; 129 private byte[] mDebugLastBackupDataRestored; 130 private byte[] mDebugLastSupplicantBackupDataRestored; 131 WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil)132 public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) { 133 mWifiPermissionsUtil = wifiPermissionsUtil; 134 } 135 136 /** 137 * Retrieve an XML byte stream representing the data that needs to be backed up from the 138 * provided configurations. 139 * 140 * @param configurations list of currently saved networks that needs to be backed up. 141 * @return Raw byte stream of XML that needs to be backed up. 142 */ retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations)143 public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) { 144 if (configurations == null) { 145 Log.e(TAG, "Invalid configuration list received"); 146 return new byte[0]; 147 } 148 149 try { 150 final XmlSerializer out = new FastXmlSerializer(); 151 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 152 out.setOutput(outputStream, StandardCharsets.UTF_8.name()); 153 154 // Start writing the XML stream. 155 XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); 156 157 XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION); 158 159 writeNetworkConfigurationsToXml(out, configurations); 160 161 XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER); 162 163 byte[] data = outputStream.toByteArray(); 164 165 if (mVerboseLoggingEnabled) { 166 mDebugLastBackupDataRetrieved = data; 167 } 168 169 return data; 170 } catch (XmlPullParserException e) { 171 Log.e(TAG, "Error retrieving the backup data: " + e); 172 } catch (IOException e) { 173 Log.e(TAG, "Error retrieving the backup data: " + e); 174 } 175 return new byte[0]; 176 } 177 178 /** 179 * Write the list of configurations to the XML stream. 180 */ writeNetworkConfigurationsToXml( XmlSerializer out, List<WifiConfiguration> configurations)181 private void writeNetworkConfigurationsToXml( 182 XmlSerializer out, List<WifiConfiguration> configurations) 183 throws XmlPullParserException, IOException { 184 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); 185 for (WifiConfiguration configuration : configurations) { 186 // We don't want to backup/restore enterprise/passpoint configurations. 187 if (configuration.isEnterprise() || configuration.isPasspoint()) { 188 continue; 189 } 190 if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) { 191 Log.d(TAG, "Ignoring network from an app with no config override permission: " 192 + configuration.configKey()); 193 continue; 194 } 195 // Write this configuration data now. 196 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK); 197 writeNetworkConfigurationToXml(out, configuration); 198 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK); 199 } 200 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); 201 } 202 203 /** 204 * Write the configuration data elements from the provided Configuration to the XML stream. 205 * Uses XmlUtils to write the values of each element. 206 */ writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)207 private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration) 208 throws XmlPullParserException, IOException { 209 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); 210 WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration); 211 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); 212 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); 213 IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration()); 214 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); 215 } 216 217 /** 218 * Parse out the configurations from the back up data. 219 * 220 * @param data raw byte stream representing the XML data. 221 * @return list of networks retrieved from the backed up data. 222 */ retrieveConfigurationsFromBackupData(byte[] data)223 public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) { 224 if (data == null || data.length == 0) { 225 Log.e(TAG, "Invalid backup data received"); 226 return null; 227 } 228 try { 229 if (mVerboseLoggingEnabled) { 230 mDebugLastBackupDataRestored = data; 231 } 232 233 final XmlPullParser in = Xml.newPullParser(); 234 ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 235 in.setInput(inputStream, StandardCharsets.UTF_8.name()); 236 237 // Start parsing the XML stream. 238 XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER); 239 int rootTagDepth = in.getDepth(); 240 241 int majorVersion = -1; 242 int minorVersion = -1; 243 try { 244 float version = (float) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION); 245 246 // parse out major and minor versions 247 String versionStr = new Float(version).toString(); 248 int separatorPos = versionStr.indexOf('.'); 249 if (separatorPos == -1) { 250 majorVersion = Integer.parseInt(versionStr); 251 minorVersion = 0; 252 } else { 253 majorVersion = Integer.parseInt(versionStr.substring(0, separatorPos)); 254 minorVersion = Integer.parseInt(versionStr.substring(separatorPos + 1)); 255 } 256 } catch (ClassCastException cce) { 257 // Integer cannot be cast to Float for data coming from before Android P 258 majorVersion = 1; 259 minorVersion = 0; 260 } 261 Log.d(TAG, "Version of backup data - major: " + majorVersion 262 + "; minor: " + minorVersion); 263 264 WifiBackupDataParser parser = getWifiBackupDataParser(majorVersion); 265 if (parser == null) { 266 Log.w(TAG, "Major version of backup data is unknown to this Android" 267 + " version; not restoring"); 268 return null; 269 } else { 270 return parser.parseNetworkConfigurationsFromXml(in, rootTagDepth, minorVersion); 271 } 272 } catch (XmlPullParserException | IOException | ClassCastException 273 | IllegalArgumentException e) { 274 Log.e(TAG, "Error parsing the backup data: " + e); 275 } 276 return null; 277 } 278 getWifiBackupDataParser(int majorVersion)279 private WifiBackupDataParser getWifiBackupDataParser(int majorVersion) { 280 switch (majorVersion) { 281 case INITIAL_BACKUP_DATA_VERSION: 282 return new WifiBackupDataV1Parser(); 283 default: 284 Log.e(TAG, "Unrecognized majorVersion of backup data: " + majorVersion); 285 return null; 286 } 287 } 288 289 /** 290 * Create log dump of the backup data in XML format with the preShared & WEP key masked. 291 * 292 * PSK keys are written in the following format in XML: 293 * <string name="PreSharedKey">WifiBackupRestorePsk</string> 294 * 295 * WEP Keys are written in following format in XML: 296 * <string-array name="WEPKeys" num="4"> 297 * <item value="WifiBackupRestoreWep1" /> 298 * <item value="WifiBackupRestoreWep2" /> 299 * <item value="WifiBackupRestoreWep3" /> 300 * <item value="WifiBackupRestoreWep3" /> 301 * </string-array> 302 */ createLogFromBackupData(byte[] data)303 private String createLogFromBackupData(byte[] data) { 304 StringBuilder sb = new StringBuilder(); 305 try { 306 String xmlString = new String(data, StandardCharsets.UTF_8.name()); 307 boolean wepKeysLine = false; 308 for (String line : xmlString.split("\n")) { 309 if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { 310 line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); 311 } 312 if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) { 313 wepKeysLine = true; 314 } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) { 315 wepKeysLine = false; 316 } else if (wepKeysLine) { 317 line = line.replaceAll( 318 WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); 319 } 320 sb.append(line).append("\n"); 321 } 322 } catch (UnsupportedEncodingException e) { 323 return ""; 324 } 325 return sb.toString(); 326 } 327 328 /** 329 * Restore state from the older supplicant back up data. 330 * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. 331 * 332 * @param supplicantData Raw byte stream of wpa_supplicant.conf 333 * @param ipConfigData Raw byte stream of ipconfig.txt 334 * @return list of networks retrieved from the backed up data. 335 */ retrieveConfigurationsFromSupplicantBackupData( byte[] supplicantData, byte[] ipConfigData)336 public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData( 337 byte[] supplicantData, byte[] ipConfigData) { 338 if (supplicantData == null || supplicantData.length == 0) { 339 Log.e(TAG, "Invalid supplicant backup data received"); 340 return null; 341 } 342 343 if (mVerboseLoggingEnabled) { 344 mDebugLastSupplicantBackupDataRestored = supplicantData; 345 } 346 347 SupplicantBackupMigration.SupplicantNetworks supplicantNetworks = 348 new SupplicantBackupMigration.SupplicantNetworks(); 349 // Incorporate the networks present in the backup data. 350 char[] restoredAsChars = new char[supplicantData.length]; 351 for (int i = 0; i < supplicantData.length; i++) { 352 restoredAsChars[i] = (char) supplicantData[i]; 353 } 354 355 BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars)); 356 supplicantNetworks.readNetworksFromStream(in); 357 358 // Retrieve corresponding WifiConfiguration objects. 359 List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations(); 360 361 // Now retrieve all the IpConfiguration objects and set in the corresponding 362 // WifiConfiguration objects if ipconfig data is present. 363 if (ipConfigData != null && ipConfigData.length != 0) { 364 SparseArray<IpConfiguration> networks = 365 IpConfigStore.readIpAndProxyConfigurations( 366 new ByteArrayInputStream(ipConfigData)); 367 if (networks != null) { 368 for (int i = 0; i < networks.size(); i++) { 369 int id = networks.keyAt(i); 370 for (WifiConfiguration configuration : configurations) { 371 // This is a dangerous lookup, but that's how it is currently written. 372 if (configuration.configKey().hashCode() == id) { 373 configuration.setIpConfiguration(networks.valueAt(i)); 374 } 375 } 376 } 377 } else { 378 Log.e(TAG, "Failed to parse ipconfig data"); 379 } 380 } else { 381 Log.e(TAG, "Invalid ipconfig backup data received"); 382 } 383 return configurations; 384 } 385 386 /** 387 * Enable verbose logging. 388 * 389 * @param verbose verbosity level. 390 */ enableVerboseLogging(int verbose)391 public void enableVerboseLogging(int verbose) { 392 mVerboseLoggingEnabled = (verbose > 0); 393 if (!mVerboseLoggingEnabled) { 394 mDebugLastBackupDataRetrieved = null; 395 mDebugLastBackupDataRestored = null; 396 mDebugLastSupplicantBackupDataRestored = null; 397 } 398 } 399 400 /** 401 * Dump out the last backup/restore data if verbose logging is enabled. 402 * 403 * @param fd unused 404 * @param pw PrintWriter for writing dump to 405 * @param args unused 406 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)407 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 408 pw.println("Dump of WifiBackupRestore"); 409 if (mDebugLastBackupDataRetrieved != null) { 410 pw.println("Last backup data retrieved: " 411 + createLogFromBackupData(mDebugLastBackupDataRetrieved)); 412 } 413 if (mDebugLastBackupDataRestored != null) { 414 pw.println("Last backup data restored: " 415 + createLogFromBackupData(mDebugLastBackupDataRestored)); 416 } 417 if (mDebugLastSupplicantBackupDataRestored != null) { 418 pw.println("Last old backup data restored: " 419 + SupplicantBackupMigration.createLogFromBackupData( 420 mDebugLastSupplicantBackupDataRestored)); 421 } 422 } 423 424 /** 425 * These sub classes contain the logic to parse older backups and restore wifi state from it. 426 * Most of the code here has been migrated over from BackupSettingsAgent. 427 * This is kind of ugly text parsing, but it is needed to support the migration of this data. 428 */ 429 public static class SupplicantBackupMigration { 430 /** 431 * List of keys to look out for in wpa_supplicant.conf parsing. 432 * These key values are declared in different parts of the wifi codebase today. 433 */ 434 public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName; 435 public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName; 436 public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName; 437 public static final String SUPPLICANT_KEY_CLIENT_CERT = 438 WifiEnterpriseConfig.CLIENT_CERT_KEY; 439 public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY; 440 public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY; 441 public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY; 442 public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName; 443 public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0]; 444 public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1]; 445 public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2]; 446 public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3]; 447 public static final String SUPPLICANT_KEY_WEP_KEY_IDX = 448 WifiConfiguration.wepTxKeyIdxVarName; 449 public static final String SUPPLICANT_KEY_ID_STR = "id_str"; 450 451 /** 452 * Regex to mask out passwords in backup data dump. 453 */ 454 private static final String PSK_MASK_LINE_MATCH_PATTERN = 455 ".*" + SUPPLICANT_KEY_PSK + ".*=.*"; 456 private static final String PSK_MASK_SEARCH_PATTERN = 457 "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)"; 458 private static final String PSK_MASK_REPLACE_PATTERN = "$1*"; 459 460 private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN = 461 ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*"; 462 private static final String WEP_KEYS_MASK_SEARCH_PATTERN = 463 "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)"; 464 private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*"; 465 466 /** 467 * Create log dump of the backup data in wpa_supplicant.conf format with the preShared & 468 * WEP key masked. 469 * 470 * PSK keys are written in the following format in wpa_supplicant.conf: 471 * psk=WifiBackupRestorePsk 472 * 473 * WEP Keys are written in following format in wpa_supplicant.conf: 474 * wep_keys0=WifiBackupRestoreWep0 475 * wep_keys1=WifiBackupRestoreWep1 476 * wep_keys2=WifiBackupRestoreWep2 477 * wep_keys3=WifiBackupRestoreWep3 478 */ createLogFromBackupData(byte[] data)479 public static String createLogFromBackupData(byte[] data) { 480 StringBuilder sb = new StringBuilder(); 481 try { 482 String supplicantConfString = new String(data, StandardCharsets.UTF_8.name()); 483 for (String line : supplicantConfString.split("\n")) { 484 if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { 485 line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); 486 } 487 if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) { 488 line = line.replaceAll( 489 WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); 490 } 491 sb.append(line).append("\n"); 492 } 493 } catch (UnsupportedEncodingException e) { 494 return ""; 495 } 496 return sb.toString(); 497 } 498 499 /** 500 * Class for capturing a network definition from the wifi supplicant config file. 501 */ 502 static class SupplicantNetwork { 503 private String mParsedSSIDLine; 504 private String mParsedHiddenLine; 505 private String mParsedKeyMgmtLine; 506 private String mParsedPskLine; 507 private String[] mParsedWepKeyLines = new String[4]; 508 private String mParsedWepTxKeyIdxLine; 509 private String mParsedIdStrLine; 510 public boolean certUsed = false; 511 public boolean isEap = false; 512 513 /** 514 * Read lines from wpa_supplicant.conf stream for this network. 515 */ readNetworkFromStream(BufferedReader in)516 public static SupplicantNetwork readNetworkFromStream(BufferedReader in) { 517 final SupplicantNetwork n = new SupplicantNetwork(); 518 String line; 519 try { 520 while (in.ready()) { 521 line = in.readLine(); 522 if (line == null || line.startsWith("}")) { 523 break; 524 } 525 n.parseLine(line); 526 } 527 } catch (IOException e) { 528 return null; 529 } 530 return n; 531 } 532 533 /** 534 * Parse a line from wpa_supplicant.conf stream for this network. 535 */ parseLine(String line)536 void parseLine(String line) { 537 // Can't rely on particular whitespace patterns so strip leading/trailing. 538 line = line.trim(); 539 if (line.isEmpty()) return; // only whitespace; drop the line. 540 541 // Now parse the network block within wpa_supplicant.conf and store the important 542 // lines for processing later. 543 if (line.startsWith(SUPPLICANT_KEY_SSID + "=")) { 544 mParsedSSIDLine = line; 545 } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN + "=")) { 546 mParsedHiddenLine = line; 547 } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT + "=")) { 548 mParsedKeyMgmtLine = line; 549 if (line.contains("EAP")) { 550 isEap = true; 551 } 552 } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) { 553 certUsed = true; 554 } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) { 555 certUsed = true; 556 } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH + "=")) { 557 certUsed = true; 558 } else if (line.startsWith(SUPPLICANT_KEY_EAP + "=")) { 559 isEap = true; 560 } else if (line.startsWith(SUPPLICANT_KEY_PSK + "=")) { 561 mParsedPskLine = line; 562 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0 + "=")) { 563 mParsedWepKeyLines[0] = line; 564 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1 + "=")) { 565 mParsedWepKeyLines[1] = line; 566 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2 + "=")) { 567 mParsedWepKeyLines[2] = line; 568 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3 + "=")) { 569 mParsedWepKeyLines[3] = line; 570 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX + "=")) { 571 mParsedWepTxKeyIdxLine = line; 572 } else if (line.startsWith(SUPPLICANT_KEY_ID_STR + "=")) { 573 mParsedIdStrLine = line; 574 } 575 } 576 577 /** 578 * Create WifiConfiguration object from the parsed data for this network. 579 */ createWifiConfiguration()580 public WifiConfiguration createWifiConfiguration() { 581 if (mParsedSSIDLine == null) { 582 // No SSID => malformed network definition 583 return null; 584 } 585 WifiConfiguration configuration = new WifiConfiguration(); 586 configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1); 587 588 if (mParsedHiddenLine != null) { 589 // Can't use Boolean.valueOf() because it works only for true/false. 590 configuration.hiddenSSID = 591 Integer.parseInt(mParsedHiddenLine.substring( 592 mParsedHiddenLine.indexOf('=') + 1)) != 0; 593 } 594 if (mParsedKeyMgmtLine == null) { 595 // no key_mgmt line specified; this is defined as equivalent to 596 // "WPA-PSK WPA-EAP". 597 configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); 598 configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); 599 } else { 600 // Need to parse the mParsedKeyMgmtLine line 601 final String bareKeyMgmt = 602 mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1); 603 String[] typeStrings = bareKeyMgmt.split("\\s+"); 604 605 // Parse out all the key management regimes permitted for this network. 606 // The literal strings here are the standard values permitted in 607 // wpa_supplicant.conf. 608 for (int i = 0; i < typeStrings.length; i++) { 609 final String ktype = typeStrings[i]; 610 if (ktype.equals("NONE")) { 611 configuration.allowedKeyManagement.set( 612 WifiConfiguration.KeyMgmt.NONE); 613 } else if (ktype.equals("WPA-PSK")) { 614 configuration.allowedKeyManagement.set( 615 WifiConfiguration.KeyMgmt.WPA_PSK); 616 } else if (ktype.equals("WPA-EAP")) { 617 configuration.allowedKeyManagement.set( 618 WifiConfiguration.KeyMgmt.WPA_EAP); 619 } else if (ktype.equals("IEEE8021X")) { 620 configuration.allowedKeyManagement.set( 621 WifiConfiguration.KeyMgmt.IEEE8021X); 622 } 623 } 624 } 625 if (mParsedPskLine != null) { 626 configuration.preSharedKey = 627 mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1); 628 } 629 if (mParsedWepKeyLines[0] != null) { 630 configuration.wepKeys[0] = 631 mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1); 632 } 633 if (mParsedWepKeyLines[1] != null) { 634 configuration.wepKeys[1] = 635 mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1); 636 } 637 if (mParsedWepKeyLines[2] != null) { 638 configuration.wepKeys[2] = 639 mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1); 640 } 641 if (mParsedWepKeyLines[3] != null) { 642 configuration.wepKeys[3] = 643 mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1); 644 } 645 if (mParsedWepTxKeyIdxLine != null) { 646 configuration.wepTxKeyIndex = 647 Integer.valueOf(mParsedWepTxKeyIdxLine.substring( 648 mParsedWepTxKeyIdxLine.indexOf('=') + 1)); 649 } 650 if (mParsedIdStrLine != null) { 651 String idString = 652 mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1); 653 if (idString != null) { 654 Map<String, String> extras = 655 SupplicantStaNetworkHal.parseNetworkExtra( 656 NativeUtil.removeEnclosingQuotes(idString)); 657 if (extras == null) { 658 Log.e(TAG, "Error parsing network extras, ignoring network."); 659 return null; 660 } 661 String configKey = extras.get( 662 SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY); 663 // No ConfigKey was passed but we need it for validating the parsed 664 // network so we stop the restore. 665 if (configKey == null) { 666 Log.e(TAG, "Configuration key was not passed, ignoring network."); 667 return null; 668 } 669 if (!configKey.equals(configuration.configKey())) { 670 // ConfigKey mismatches are expected for private networks because the 671 // UID is not preserved across backup/restore. 672 Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey 673 + ", Calculated: " + configuration.configKey()); 674 } 675 // For wpa_supplicant backup data, parse out the creatorUid to ensure that 676 // these networks were created by system apps. 677 int creatorUid = 678 Integer.parseInt(extras.get( 679 SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID)); 680 if (creatorUid >= Process.FIRST_APPLICATION_UID) { 681 Log.d(TAG, "Ignoring network from non-system app: " 682 + configuration.configKey()); 683 return null; 684 } 685 } 686 } 687 return configuration; 688 } 689 } 690 691 /** 692 * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={} 693 * blocks and eliminating duplicates 694 */ 695 static class SupplicantNetworks { 696 final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8); 697 698 /** 699 * Parse the wpa_supplicant.conf file stream and add networks. 700 */ readNetworksFromStream(BufferedReader in)701 public void readNetworksFromStream(BufferedReader in) { 702 try { 703 String line; 704 while (in.ready()) { 705 line = in.readLine(); 706 if (line != null) { 707 if (line.startsWith("network")) { 708 SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in); 709 710 // An IOException occurred while trying to read the network. 711 if (net == null) { 712 Log.e(TAG, "Error while parsing the network."); 713 continue; 714 } 715 716 // Networks that use certificates for authentication can't be 717 // restored because the certificates they need don't get restored 718 // (because they are stored in keystore, and can't be restored). 719 // Similarly, omit EAP network definitions to avoid propagating 720 // controlled enterprise network definitions. 721 if (net.isEap || net.certUsed) { 722 Log.d(TAG, "Skipping enterprise network for restore: " 723 + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine); 724 continue; 725 } 726 mNetworks.add(net); 727 } 728 } 729 } 730 } catch (IOException e) { 731 // whatever happened, we're done now 732 } 733 } 734 735 /** 736 * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf 737 */ retrieveWifiConfigurations()738 public List<WifiConfiguration> retrieveWifiConfigurations() { 739 ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); 740 for (SupplicantNetwork net : mNetworks) { 741 try { 742 WifiConfiguration wifiConfiguration = net.createWifiConfiguration(); 743 if (wifiConfiguration != null) { 744 Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey()); 745 wifiConfigurations.add(wifiConfiguration); 746 } 747 } catch (NumberFormatException e) { 748 // Occurs if we are unable to parse the hidden SSID, WEP Key index or 749 // creator UID. 750 Log.e(TAG, "Error parsing wifi configuration: " + e); 751 return null; 752 } 753 } 754 return wifiConfigurations; 755 } 756 } 757 } 758 } 759