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