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