• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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