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.wifi.util.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 int CURRENT_BACKUP_DATA_MAJOR_VERSION = 1; 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 private byte[] mDebugLastIpConfigBackupDataRestored; 132 WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil)133 public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) { 134 mWifiPermissionsUtil = wifiPermissionsUtil; 135 } 136 137 /** 138 * Retrieve the version for serialization. 139 */ getVersion()140 private Float getVersion() { 141 WifiBackupDataParser parser = 142 getWifiBackupDataParser(CURRENT_BACKUP_DATA_MAJOR_VERSION); 143 if (parser == null) { 144 Log.e(TAG, "Major version of backup data is unknown to this Android" 145 + " version; not backing up"); 146 return null; 147 } 148 int minorVersion = parser.getHighestSupportedMinorVersion(); 149 Float version; 150 try { 151 version = Float.valueOf( 152 CURRENT_BACKUP_DATA_MAJOR_VERSION + "." + minorVersion); 153 } catch (NumberFormatException e) { 154 Log.e(TAG, "Failed to generate version", e); 155 return null; 156 } 157 return version; 158 } 159 160 /** 161 * Retrieve an XML byte stream representing the data that needs to be backed up from the 162 * provided configurations. 163 * 164 * @param configurations list of currently saved networks that needs to be backed up. 165 * @return Raw byte stream of XML that needs to be backed up. 166 */ retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations)167 public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) { 168 if (configurations == null) { 169 Log.e(TAG, "Invalid configuration list received"); 170 return new byte[0]; 171 } 172 173 try { 174 final XmlSerializer out = new FastXmlSerializer(); 175 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 176 out.setOutput(outputStream, StandardCharsets.UTF_8.name()); 177 178 // Start writing the XML stream. 179 XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); 180 181 Float version = getVersion(); 182 if (version == null) return null; 183 XmlUtil.writeNextValue(out, XML_TAG_VERSION, version.floatValue()); 184 185 writeNetworkConfigurationsToXml(out, configurations); 186 187 XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER); 188 189 byte[] data = outputStream.toByteArray(); 190 191 if (mVerboseLoggingEnabled) { 192 mDebugLastBackupDataRetrieved = data; 193 } 194 195 return data; 196 } catch (XmlPullParserException e) { 197 Log.e(TAG, "Error retrieving the backup data: " + e); 198 } catch (IOException e) { 199 Log.e(TAG, "Error retrieving the backup data: " + e); 200 } 201 return new byte[0]; 202 } 203 204 /** 205 * Write the list of configurations to the XML stream. 206 */ writeNetworkConfigurationsToXml( XmlSerializer out, List<WifiConfiguration> configurations)207 private void writeNetworkConfigurationsToXml( 208 XmlSerializer out, List<WifiConfiguration> configurations) 209 throws XmlPullParserException, IOException { 210 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); 211 for (WifiConfiguration configuration : configurations) { 212 // We don't want to backup/restore enterprise/passpoint configurations. 213 if (configuration.isEnterprise() || configuration.isPasspoint()) { 214 continue; 215 } 216 if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) { 217 Log.d(TAG, "Ignoring network from an app with no config override permission: " 218 + configuration.getKey()); 219 continue; 220 } 221 // Skip if user has never connected due to wrong password. 222 if (!configuration.getNetworkSelectionStatus().hasEverConnected()) { 223 int disableReason = configuration.getNetworkSelectionStatus() 224 .getNetworkSelectionDisableReason(); 225 if (disableReason 226 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD) { 227 continue; 228 } 229 } 230 // Write this configuration data now. 231 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK); 232 writeNetworkConfigurationToXml(out, configuration); 233 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK); 234 } 235 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); 236 } 237 238 /** 239 * Write the configuration data elements from the provided Configuration to the XML stream. 240 * Uses XmlUtils to write the values of each element. 241 */ writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)242 private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration) 243 throws XmlPullParserException, IOException { 244 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); 245 WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration); 246 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); 247 XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); 248 IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration()); 249 XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); 250 } 251 252 /** 253 * Parse out the configurations from the back up data. 254 * 255 * @param data raw byte stream representing the XML data. 256 * @return list of networks retrieved from the backed up data. 257 */ retrieveConfigurationsFromBackupData(byte[] data)258 public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) { 259 if (data == null || data.length == 0) { 260 Log.e(TAG, "Invalid backup data received"); 261 return null; 262 } 263 try { 264 if (mVerboseLoggingEnabled) { 265 mDebugLastBackupDataRestored = data; 266 } 267 268 final XmlPullParser in = Xml.newPullParser(); 269 ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 270 in.setInput(inputStream, StandardCharsets.UTF_8.name()); 271 272 // Start parsing the XML stream. 273 XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER); 274 int rootTagDepth = in.getDepth(); 275 276 int majorVersion = -1; 277 int minorVersion = -1; 278 try { 279 float version = (float) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION); 280 281 // parse out major and minor versions 282 String versionStr = new Float(version).toString(); 283 int separatorPos = versionStr.indexOf('.'); 284 if (separatorPos == -1) { 285 majorVersion = Integer.parseInt(versionStr); 286 minorVersion = 0; 287 } else { 288 majorVersion = Integer.parseInt(versionStr.substring(0, separatorPos)); 289 minorVersion = Integer.parseInt(versionStr.substring(separatorPos + 1)); 290 } 291 } catch (ClassCastException cce) { 292 // Integer cannot be cast to Float for data coming from before Android P 293 majorVersion = 1; 294 minorVersion = 0; 295 } 296 Log.d(TAG, "Version of backup data - major: " + majorVersion 297 + "; minor: " + minorVersion); 298 299 WifiBackupDataParser parser = getWifiBackupDataParser(majorVersion); 300 if (parser == null) { 301 Log.w(TAG, "Major version of backup data is unknown to this Android" 302 + " version; not restoring"); 303 return null; 304 } else { 305 return parser.parseNetworkConfigurationsFromXml(in, rootTagDepth, minorVersion); 306 } 307 } catch (XmlPullParserException | IOException | ClassCastException 308 | IllegalArgumentException e) { 309 Log.e(TAG, "Error parsing the backup data: " + e); 310 } 311 return null; 312 } 313 getWifiBackupDataParser(int majorVersion)314 private WifiBackupDataParser getWifiBackupDataParser(int majorVersion) { 315 switch (majorVersion) { 316 case INITIAL_BACKUP_DATA_VERSION: 317 return new WifiBackupDataV1Parser(); 318 default: 319 Log.e(TAG, "Unrecognized majorVersion of backup data: " + majorVersion); 320 return null; 321 } 322 } 323 324 /** 325 * Create log dump of the backup data in XML format with the preShared & WEP key masked. 326 * 327 * PSK keys are written in the following format in XML: 328 * <string name="PreSharedKey">WifiBackupRestorePsk</string> 329 * 330 * WEP Keys are written in following format in XML: 331 * <string-array name="WEPKeys" num="4"> 332 * <item value="WifiBackupRestoreWep1" /> 333 * <item value="WifiBackupRestoreWep2" /> 334 * <item value="WifiBackupRestoreWep3" /> 335 * <item value="WifiBackupRestoreWep3" /> 336 * </string-array> 337 */ createLogFromBackupData(byte[] data)338 private String createLogFromBackupData(byte[] data) { 339 StringBuilder sb = new StringBuilder(); 340 try { 341 String xmlString = new String(data, StandardCharsets.UTF_8.name()); 342 boolean wepKeysLine = false; 343 for (String line : xmlString.split("\n")) { 344 if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { 345 line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); 346 } 347 if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) { 348 wepKeysLine = true; 349 } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) { 350 wepKeysLine = false; 351 } else if (wepKeysLine) { 352 line = line.replaceAll( 353 WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); 354 } 355 sb.append(line).append("\n"); 356 } 357 } catch (UnsupportedEncodingException e) { 358 return ""; 359 } 360 return sb.toString(); 361 } 362 363 /** 364 * Restore state from the older supplicant back up data. 365 * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. 366 * 367 * @param supplicantData Raw byte stream of wpa_supplicant.conf 368 * @param ipConfigData Raw byte stream of ipconfig.txt 369 * @return list of networks retrieved from the backed up data. 370 */ retrieveConfigurationsFromSupplicantBackupData( byte[] supplicantData, byte[] ipConfigData)371 public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData( 372 byte[] supplicantData, byte[] ipConfigData) { 373 if (supplicantData == null || supplicantData.length == 0) { 374 Log.e(TAG, "Invalid supplicant backup data received"); 375 return null; 376 } 377 378 if (mVerboseLoggingEnabled) { 379 mDebugLastSupplicantBackupDataRestored = supplicantData; 380 mDebugLastIpConfigBackupDataRestored = ipConfigData; 381 } 382 383 SupplicantBackupMigration.SupplicantNetworks supplicantNetworks = 384 new SupplicantBackupMigration.SupplicantNetworks(); 385 // Incorporate the networks present in the backup data. 386 char[] restoredAsChars = new char[supplicantData.length]; 387 for (int i = 0; i < supplicantData.length; i++) { 388 restoredAsChars[i] = (char) supplicantData[i]; 389 } 390 391 BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars)); 392 supplicantNetworks.readNetworksFromStream(in); 393 394 // Retrieve corresponding WifiConfiguration objects. 395 List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations(); 396 397 // Now retrieve all the IpConfiguration objects and set in the corresponding 398 // WifiConfiguration objects if ipconfig data is present. 399 if (ipConfigData != null && ipConfigData.length != 0) { 400 SparseArray<IpConfiguration> networks = 401 IpConfigStore.readIpAndProxyConfigurations( 402 new ByteArrayInputStream(ipConfigData)); 403 if (networks != null) { 404 for (int i = 0; i < networks.size(); i++) { 405 int id = networks.keyAt(i); 406 for (WifiConfiguration configuration : configurations) { 407 // This is a dangerous lookup, but that's how it is currently written. 408 if (configuration.getKey().hashCode() == id) { 409 configuration.setIpConfiguration(networks.valueAt(i)); 410 } 411 } 412 } 413 } else { 414 Log.e(TAG, "Failed to parse ipconfig data"); 415 } 416 } else { 417 Log.e(TAG, "Invalid ipconfig backup data received"); 418 } 419 return configurations; 420 } 421 422 /** 423 * Enable verbose logging. 424 * 425 * @param verbose verbosity level. 426 */ enableVerboseLogging(boolean verboseEnabled)427 public void enableVerboseLogging(boolean verboseEnabled) { 428 mVerboseLoggingEnabled = verboseEnabled; 429 if (!mVerboseLoggingEnabled) { 430 mDebugLastBackupDataRetrieved = null; 431 mDebugLastBackupDataRestored = null; 432 mDebugLastSupplicantBackupDataRestored = null; 433 mDebugLastIpConfigBackupDataRestored = null; 434 } 435 } 436 437 /** 438 * Dump out the last backup/restore data if verbose logging is enabled. 439 * 440 * @param fd unused 441 * @param pw PrintWriter for writing dump to 442 * @param args unused 443 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)444 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 445 pw.println("Dump of WifiBackupRestore"); 446 if (mDebugLastBackupDataRetrieved != null) { 447 pw.println("Last backup data retrieved: " 448 + createLogFromBackupData(mDebugLastBackupDataRetrieved)); 449 } 450 if (mDebugLastBackupDataRestored != null) { 451 pw.println("Last backup data restored: " 452 + createLogFromBackupData(mDebugLastBackupDataRestored)); 453 } 454 if (mDebugLastSupplicantBackupDataRestored != null) { 455 pw.println("Last old supplicant backup data restored: " 456 + SupplicantBackupMigration.createLogFromBackupData( 457 mDebugLastSupplicantBackupDataRestored)); 458 } 459 if (mDebugLastIpConfigBackupDataRestored != null) { 460 pw.println("Last old ipconfig backup data restored: " 461 + mDebugLastIpConfigBackupDataRestored); 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_AUTH_ALG = 479 WifiConfiguration.AuthAlgorithm.varName; 480 public static final String SUPPLICANT_KEY_CLIENT_CERT = 481 WifiEnterpriseConfig.CLIENT_CERT_KEY; 482 public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY; 483 public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY; 484 public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY; 485 public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName; 486 public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0]; 487 public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1]; 488 public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2]; 489 public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3]; 490 public static final String SUPPLICANT_KEY_WEP_KEY_IDX = 491 WifiConfiguration.wepTxKeyIdxVarName; 492 public static final String SUPPLICANT_KEY_ID_STR = "id_str"; 493 494 /** 495 * Regex to mask out passwords in backup data dump. 496 */ 497 private static final String PSK_MASK_LINE_MATCH_PATTERN = 498 ".*" + SUPPLICANT_KEY_PSK + ".*=.*"; 499 private static final String PSK_MASK_SEARCH_PATTERN = 500 "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)"; 501 private static final String PSK_MASK_REPLACE_PATTERN = "$1*"; 502 503 private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN = 504 ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*"; 505 private static final String WEP_KEYS_MASK_SEARCH_PATTERN = 506 "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)"; 507 private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*"; 508 509 /** 510 * Create log dump of the backup data in wpa_supplicant.conf format with the preShared & 511 * WEP key masked. 512 * 513 * PSK keys are written in the following format in wpa_supplicant.conf: 514 * psk=WifiBackupRestorePsk 515 * 516 * WEP Keys are written in following format in wpa_supplicant.conf: 517 * wep_keys0=WifiBackupRestoreWep0 518 * wep_keys1=WifiBackupRestoreWep1 519 * wep_keys2=WifiBackupRestoreWep2 520 * wep_keys3=WifiBackupRestoreWep3 521 */ createLogFromBackupData(byte[] data)522 public static String createLogFromBackupData(byte[] data) { 523 StringBuilder sb = new StringBuilder(); 524 try { 525 String supplicantConfString = new String(data, StandardCharsets.UTF_8.name()); 526 for (String line : supplicantConfString.split("\n")) { 527 if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { 528 line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); 529 } 530 if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) { 531 line = line.replaceAll( 532 WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); 533 } 534 sb.append(line).append("\n"); 535 } 536 } catch (UnsupportedEncodingException e) { 537 return ""; 538 } 539 return sb.toString(); 540 } 541 542 /** 543 * Class for capturing a network definition from the wifi supplicant config file. 544 */ 545 static class SupplicantNetwork { 546 private String mParsedSSIDLine; 547 private String mParsedHiddenLine; 548 private String mParsedKeyMgmtLine; 549 private String mParsedAuthAlgLine; 550 private String mParsedPskLine; 551 private String[] mParsedWepKeyLines = new String[4]; 552 private String mParsedWepTxKeyIdxLine; 553 private String mParsedIdStrLine; 554 public boolean certUsed = false; 555 public boolean isEap = false; 556 557 /** 558 * Read lines from wpa_supplicant.conf stream for this network. 559 */ readNetworkFromStream(BufferedReader in)560 public static SupplicantNetwork readNetworkFromStream(BufferedReader in) { 561 final SupplicantNetwork n = new SupplicantNetwork(); 562 String line; 563 try { 564 while (in.ready()) { 565 line = in.readLine(); 566 if (line == null || line.startsWith("}")) { 567 break; 568 } 569 n.parseLine(line); 570 } 571 } catch (IOException e) { 572 return null; 573 } 574 return n; 575 } 576 577 /** 578 * Parse a line from wpa_supplicant.conf stream for this network. 579 */ parseLine(String line)580 void parseLine(String line) { 581 // Can't rely on particular whitespace patterns so strip leading/trailing. 582 line = line.trim(); 583 if (line.isEmpty()) return; // only whitespace; drop the line. 584 585 // Now parse the network block within wpa_supplicant.conf and store the important 586 // lines for processing later. 587 if (line.startsWith(SUPPLICANT_KEY_SSID + "=")) { 588 mParsedSSIDLine = line; 589 } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN + "=")) { 590 mParsedHiddenLine = line; 591 } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT + "=")) { 592 mParsedKeyMgmtLine = line; 593 if (line.contains("EAP")) { 594 isEap = true; 595 } 596 } else if (line.startsWith(SUPPLICANT_KEY_AUTH_ALG + "=")) { 597 mParsedAuthAlgLine = line; 598 } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) { 599 certUsed = true; 600 } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) { 601 certUsed = true; 602 } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH + "=")) { 603 certUsed = true; 604 } else if (line.startsWith(SUPPLICANT_KEY_EAP + "=")) { 605 isEap = true; 606 } else if (line.startsWith(SUPPLICANT_KEY_PSK + "=")) { 607 mParsedPskLine = line; 608 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0 + "=")) { 609 mParsedWepKeyLines[0] = line; 610 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1 + "=")) { 611 mParsedWepKeyLines[1] = line; 612 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2 + "=")) { 613 mParsedWepKeyLines[2] = line; 614 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3 + "=")) { 615 mParsedWepKeyLines[3] = line; 616 } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX + "=")) { 617 mParsedWepTxKeyIdxLine = line; 618 } else if (line.startsWith(SUPPLICANT_KEY_ID_STR + "=")) { 619 mParsedIdStrLine = line; 620 } 621 } 622 623 /** 624 * Create WifiConfiguration object from the parsed data for this network. 625 */ createWifiConfiguration()626 public WifiConfiguration createWifiConfiguration() { 627 if (mParsedSSIDLine == null) { 628 // No SSID => malformed network definition 629 return null; 630 } 631 WifiConfiguration configuration = new WifiConfiguration(); 632 configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1); 633 634 if (mParsedHiddenLine != null) { 635 // Can't use Boolean.valueOf() because it works only for true/false. 636 configuration.hiddenSSID = 637 Integer.parseInt(mParsedHiddenLine.substring( 638 mParsedHiddenLine.indexOf('=') + 1)) != 0; 639 } 640 if (mParsedKeyMgmtLine == null) { 641 // no key_mgmt line specified; this is defined as equivalent to 642 // "WPA-PSK WPA-EAP". 643 configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); 644 configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); 645 } else { 646 // Need to parse the mParsedKeyMgmtLine line 647 final String bareKeyMgmt = 648 mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1); 649 String[] typeStrings = bareKeyMgmt.split("\\s+"); 650 651 // Parse out all the key management regimes permitted for this network. 652 // The literal strings here are the standard values permitted in 653 // wpa_supplicant.conf. 654 for (int i = 0; i < typeStrings.length; i++) { 655 final String ktype = typeStrings[i]; 656 if (ktype.equals("NONE")) { 657 configuration.allowedKeyManagement.set( 658 WifiConfiguration.KeyMgmt.NONE); 659 } else if (ktype.equals("WPA-PSK")) { 660 configuration.allowedKeyManagement.set( 661 WifiConfiguration.KeyMgmt.WPA_PSK); 662 } else if (ktype.equals("WPA-EAP")) { 663 configuration.allowedKeyManagement.set( 664 WifiConfiguration.KeyMgmt.WPA_EAP); 665 } else if (ktype.equals("IEEE8021X")) { 666 configuration.allowedKeyManagement.set( 667 WifiConfiguration.KeyMgmt.IEEE8021X); 668 } else if (ktype.equals("WAPI-PSK")) { 669 configuration.allowedKeyManagement.set( 670 WifiConfiguration.KeyMgmt.WAPI_PSK); 671 } else if (ktype.equals("WAPI-CERT")) { 672 configuration.allowedKeyManagement.set( 673 WifiConfiguration.KeyMgmt.WAPI_CERT); 674 } 675 } 676 } 677 if (mParsedAuthAlgLine != null) { 678 if (mParsedAuthAlgLine.contains("OPEN")) { 679 configuration.allowedAuthAlgorithms.set( 680 WifiConfiguration.AuthAlgorithm.OPEN); 681 } 682 if (mParsedAuthAlgLine.contains("SHARED")) { 683 configuration.allowedAuthAlgorithms.set( 684 WifiConfiguration.AuthAlgorithm.SHARED); 685 } 686 } 687 if (mParsedPskLine != null) { 688 configuration.preSharedKey = 689 mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1); 690 } 691 if (mParsedWepKeyLines[0] != null) { 692 configuration.wepKeys[0] = 693 mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1); 694 } 695 if (mParsedWepKeyLines[1] != null) { 696 configuration.wepKeys[1] = 697 mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1); 698 } 699 if (mParsedWepKeyLines[2] != null) { 700 configuration.wepKeys[2] = 701 mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1); 702 } 703 if (mParsedWepKeyLines[3] != null) { 704 configuration.wepKeys[3] = 705 mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1); 706 } 707 if (mParsedWepTxKeyIdxLine != null) { 708 configuration.wepTxKeyIndex = 709 Integer.valueOf(mParsedWepTxKeyIdxLine.substring( 710 mParsedWepTxKeyIdxLine.indexOf('=') + 1)); 711 } 712 if (mParsedIdStrLine != null) { 713 String idString = 714 mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1); 715 if (idString != null) { 716 Map<String, String> extras = 717 SupplicantStaNetworkHalHidlImpl.parseNetworkExtra( 718 NativeUtil.removeEnclosingQuotes(idString)); 719 if (extras == null) { 720 Log.e(TAG, "Error parsing network extras, ignoring network."); 721 return null; 722 } 723 String configKey = extras.get( 724 SupplicantStaNetworkHalHidlImpl.ID_STRING_KEY_CONFIG_KEY); 725 // No ConfigKey was passed but we need it for validating the parsed 726 // network so we stop the restore. 727 if (configKey == null) { 728 Log.e(TAG, "Configuration key was not passed, ignoring network."); 729 return null; 730 } 731 if (!configKey.equals(configuration.getKey())) { 732 // ConfigKey mismatches are expected for private networks because the 733 // UID is not preserved across backup/restore. 734 Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey 735 + ", Calculated: " + configuration.getKey()); 736 } 737 // For wpa_supplicant backup data, parse out the creatorUid to ensure that 738 // these networks were created by system apps. 739 int creatorUid = 740 Integer.parseInt(extras.get( 741 SupplicantStaNetworkHalHidlImpl.ID_STRING_KEY_CREATOR_UID)); 742 if (creatorUid >= Process.FIRST_APPLICATION_UID) { 743 Log.d(TAG, "Ignoring network from non-system app: " 744 + configuration.getKey()); 745 return null; 746 } 747 } 748 } 749 return configuration; 750 } 751 } 752 753 /** 754 * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={} 755 * blocks and eliminating duplicates 756 */ 757 static class SupplicantNetworks { 758 final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8); 759 760 /** 761 * Parse the wpa_supplicant.conf file stream and add networks. 762 */ readNetworksFromStream(BufferedReader in)763 public void readNetworksFromStream(BufferedReader in) { 764 try { 765 String line; 766 while (in.ready()) { 767 line = in.readLine(); 768 if (line != null) { 769 if (line.startsWith("network")) { 770 SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in); 771 772 // An IOException occurred while trying to read the network. 773 if (net == null) { 774 Log.e(TAG, "Error while parsing the network."); 775 continue; 776 } 777 778 // Networks that use certificates for authentication can't be 779 // restored because the certificates they need don't get restored 780 // (because they are stored in keystore, and can't be restored). 781 // Similarly, omit EAP network definitions to avoid propagating 782 // controlled enterprise network definitions. 783 if (net.isEap || net.certUsed) { 784 Log.d(TAG, "Skipping enterprise network for restore: " 785 + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine); 786 continue; 787 } 788 mNetworks.add(net); 789 } 790 } 791 } 792 } catch (IOException e) { 793 // whatever happened, we're done now 794 } 795 } 796 797 /** 798 * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf 799 */ retrieveWifiConfigurations()800 public List<WifiConfiguration> retrieveWifiConfigurations() { 801 ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); 802 for (SupplicantNetwork net : mNetworks) { 803 try { 804 WifiConfiguration wifiConfiguration = net.createWifiConfiguration(); 805 if (wifiConfiguration != null) { 806 Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.getKey()); 807 wifiConfigurations.add(wifiConfiguration); 808 } 809 } catch (NumberFormatException e) { 810 // Occurs if we are unable to parse the hidden SSID, WEP Key index or 811 // creator UID. 812 Log.e(TAG, "Error parsing wifi configuration: " + e); 813 return null; 814 } 815 } 816 return wifiConfigurations; 817 } 818 } 819 } 820 } 821