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