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