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