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