WifiBackupRestore.java revision b8b3fb8228a1f90106bad8c59ce006b81ef7921c
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
17package com.android.server.wifi;
18
19import android.net.IpConfiguration;
20import android.net.wifi.WifiConfiguration;
21import android.net.wifi.WifiEnterpriseConfig;
22import android.os.Process;
23import android.util.Log;
24import android.util.Pair;
25import android.util.SparseArray;
26import android.util.Xml;
27
28import com.android.internal.util.FastXmlSerializer;
29import com.android.server.net.IpConfigStore;
30import com.android.server.wifi.util.NativeUtil;
31import com.android.server.wifi.util.XmlUtil;
32import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
33import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37import org.xmlpull.v1.XmlSerializer;
38
39import java.io.BufferedReader;
40import java.io.ByteArrayInputStream;
41import java.io.ByteArrayOutputStream;
42import java.io.CharArrayReader;
43import java.io.FileDescriptor;
44import java.io.IOException;
45import java.io.PrintWriter;
46import java.io.UnsupportedEncodingException;
47import java.nio.charset.StandardCharsets;
48import java.util.ArrayList;
49import java.util.List;
50import 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 */
60public class WifiBackupRestore {
61    private static final String TAG = "WifiBackupRestore";
62
63    /**
64     * Current backup data version. This will be incremented for any additions.
65     */
66    private static final int CURRENT_BACKUP_DATA_VERSION = 1;
67
68    /** This list of older versions will be used to restore data from older backups. */
69    /**
70     * First version of the backup data format.
71     */
72    private static final int INITIAL_BACKUP_DATA_VERSION = 1;
73
74    /**
75     * List of XML section header tags in the backed up data
76     */
77    private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
78    private static final String XML_TAG_VERSION = "Version";
79    private static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
80    private static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
81    private static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
82    private static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
83
84    /**
85     * Regex to mask out passwords in backup data dump.
86     */
87    private static final String PSK_MASK_LINE_MATCH_PATTERN =
88            "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>";
89    private static final String PSK_MASK_SEARCH_PATTERN =
90            "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
91    private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3";
92
93    private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN =
94            "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">";
95    private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>";
96    private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)";
97    private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3";
98
99    /**
100     * Verbose logging flag.
101     */
102    private boolean mVerboseLoggingEnabled = false;
103
104    /**
105     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
106     * logging is enabled in developer options.
107     */
108    private byte[] mDebugLastBackupDataRetrieved;
109    private byte[] mDebugLastBackupDataRestored;
110    private byte[] mDebugLastSupplicantBackupDataRestored;
111
112    /**
113     * Retrieve an XML byte stream representing the data that needs to be backed up from the
114     * provided configurations.
115     *
116     * @param configurations list of currently saved networks that needs to be backed up.
117     * @return Raw byte stream of XML that needs to be backed up.
118     */
119    public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
120        if (configurations == null) {
121            Log.e(TAG, "Invalid configuration list received");
122            return new byte[0];
123        }
124
125        try {
126            final XmlSerializer out = new FastXmlSerializer();
127            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
128            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
129
130            // Start writing the XML stream.
131            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
132
133            XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);
134
135            writeNetworkConfigurationsToXml(out, configurations);
136
137            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
138
139            byte[] data = outputStream.toByteArray();
140
141            if (mVerboseLoggingEnabled) {
142                mDebugLastBackupDataRetrieved = data;
143            }
144
145            return data;
146        } catch (XmlPullParserException e) {
147            Log.e(TAG, "Error retrieving the backup data: " + e);
148        } catch (IOException e) {
149            Log.e(TAG, "Error retrieving the backup data: " + e);
150        }
151        return new byte[0];
152    }
153
154    /**
155     * Write the list of configurations to the XML stream.
156     */
157    private void writeNetworkConfigurationsToXml(
158            XmlSerializer out, List<WifiConfiguration> configurations)
159            throws XmlPullParserException, IOException {
160        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
161        for (WifiConfiguration configuration : configurations) {
162            // We don't want to backup/restore enterprise/passpoint configurations.
163            if (configuration.isEnterprise() || configuration.isPasspoint()) {
164                continue;
165            }
166            if (configuration.creatorUid >= Process.FIRST_APPLICATION_UID) {
167                continue;
168            }
169            // Write this configuration data now.
170            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
171            writeNetworkConfigurationToXml(out, configuration);
172            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
173        }
174        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
175    }
176
177    /**
178     * Write the configuration data elements from the provided Configuration to the XML stream.
179     * Uses XmlUtils to write the values of each element.
180     */
181    private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
182            throws XmlPullParserException, IOException {
183        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
184        WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration);
185        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
186        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
187        IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration());
188        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
189    }
190
191    /**
192     * Parse out the configurations from the back up data.
193     *
194     * @param data raw byte stream representing the XML data.
195     * @return list of networks retrieved from the backed up data.
196     */
197    public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
198        if (data == null || data.length == 0) {
199            Log.e(TAG, "Invalid backup data received");
200            return null;
201        }
202
203        try {
204            if (mVerboseLoggingEnabled) {
205                mDebugLastBackupDataRestored = data;
206            }
207
208            final XmlPullParser in = Xml.newPullParser();
209            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
210            in.setInput(inputStream, StandardCharsets.UTF_8.name());
211
212            // Start parsing the XML stream.
213            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
214            int rootTagDepth = in.getDepth();
215
216            int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
217            if (version < INITIAL_BACKUP_DATA_VERSION || version > CURRENT_BACKUP_DATA_VERSION) {
218                Log.e(TAG, "Invalid version of data: " + version);
219                return null;
220            }
221
222            return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
223        } catch (XmlPullParserException e) {
224            Log.e(TAG, "Error parsing the backup data: " + e);
225        } catch (IOException e) {
226            Log.e(TAG, "Error parsing the backup data: " + e);
227        }
228        return null;
229    }
230
231    /**
232     * Parses the list of configurations from the provided XML stream.
233     *
234     * @param in            XmlPullParser instance pointing to the XML stream.
235     * @param outerTagDepth depth of the outer tag in the XML document.
236     * @param dataVersion   version number parsed from incoming data.
237     * @return List<WifiConfiguration> object if parsing is successful, null otherwise.
238     */
239    private List<WifiConfiguration> parseNetworkConfigurationsFromXml(
240            XmlPullParser in, int outerTagDepth, int dataVersion)
241            throws XmlPullParserException, IOException {
242        // Find the configuration list section.
243        XmlUtil.gotoNextSectionWithName(in, XML_TAG_SECTION_HEADER_NETWORK_LIST, outerTagDepth);
244        // Find all the configurations within the configuration list section.
245        int networkListTagDepth = outerTagDepth + 1;
246        List<WifiConfiguration> configurations = new ArrayList<>();
247        while (XmlUtil.gotoNextSectionWithNameOrEnd(
248                in, XML_TAG_SECTION_HEADER_NETWORK, networkListTagDepth)) {
249            WifiConfiguration configuration =
250                    parseNetworkConfigurationFromXml(in, dataVersion, networkListTagDepth);
251            if (configuration != null) {
252                Log.v(TAG, "Parsed Configuration: " + configuration.configKey());
253                configurations.add(configuration);
254            }
255        }
256        return configurations;
257    }
258
259    /**
260     * Helper method to parse the WifiConfiguration object and validate the configKey parsed.
261     */
262    private WifiConfiguration parseWifiConfigurationFromXmlAndValidateConfigKey(
263            XmlPullParser in, int outerTagDepth)
264            throws XmlPullParserException, IOException {
265        Pair<String, WifiConfiguration> parsedConfig =
266                WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth);
267        if (parsedConfig == null || parsedConfig.first == null || parsedConfig.second == null) {
268            return null;
269        }
270        String configKeyParsed = parsedConfig.first;
271        WifiConfiguration configuration = parsedConfig.second;
272        String configKeyCalculated = configuration.configKey();
273        if (!configKeyParsed.equals(configKeyCalculated)) {
274            String configKeyMismatchLog =
275                    "Configuration key does not match. Retrieved: " + configKeyParsed
276                            + ", Calculated: " + configKeyCalculated;
277            if (configuration.shared) {
278                Log.e(TAG, configKeyMismatchLog);
279                return null;
280            } else {
281                // ConfigKey mismatches are expected for private networks because the
282                // UID is not preserved across backup/restore.
283                Log.w(TAG, configKeyMismatchLog);
284            }
285        }
286       return configuration;
287    }
288
289    /**
290     * Parses the configuration data elements from the provided XML stream to a Configuration.
291     *
292     * @param in            XmlPullParser instance pointing to the XML stream.
293     * @param outerTagDepth depth of the outer tag in the XML document.
294     * @param dataVersion   version number parsed from incoming data.
295     * @return WifiConfiguration object if parsing is successful, null otherwise.
296     */
297    private WifiConfiguration parseNetworkConfigurationFromXml(XmlPullParser in, int dataVersion,
298            int outerTagDepth)
299            throws XmlPullParserException, IOException {
300        // Any version migration needs to be handled here in future.
301        if (dataVersion == INITIAL_BACKUP_DATA_VERSION) {
302            WifiConfiguration configuration = null;
303            int networkTagDepth = outerTagDepth + 1;
304            // Retrieve WifiConfiguration object first.
305            XmlUtil.gotoNextSectionWithName(
306                    in, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION, networkTagDepth);
307            int configTagDepth = networkTagDepth + 1;
308            configuration = parseWifiConfigurationFromXmlAndValidateConfigKey(in, configTagDepth);
309            if (configuration == null) {
310                return null;
311            }
312            // Now retrieve any IP configuration info.
313            XmlUtil.gotoNextSectionWithName(
314                    in, XML_TAG_SECTION_HEADER_IP_CONFIGURATION, networkTagDepth);
315            IpConfiguration ipConfiguration =
316                    IpConfigurationXmlUtil.parseFromXml(in, configTagDepth);
317            configuration.setIpConfiguration(ipConfiguration);
318            return configuration;
319        }
320        return null;
321    }
322
323    /**
324     * Create log dump of the backup data in XML format with the preShared & WEP key masked.
325     *
326     * PSK keys are written in the following format in XML:
327     * <string name="PreSharedKey">WifiBackupRestorePsk</string>
328     *
329     * WEP Keys are written in following format in XML:
330     * <string-array name="WEPKeys" num="4">
331     *  <item value="WifiBackupRestoreWep1" />
332     *  <item value="WifiBackupRestoreWep2" />
333     *  <item value="WifiBackupRestoreWep3" />
334     *  <item value="WifiBackupRestoreWep3" />
335     * </string-array>
336     */
337    private String createLogFromBackupData(byte[] data) {
338        StringBuilder sb = new StringBuilder();
339        try {
340            String xmlString = new String(data, StandardCharsets.UTF_8.name());
341            boolean wepKeysLine = false;
342            for (String line : xmlString.split("\n")) {
343                if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
344                    line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
345                }
346                if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) {
347                    wepKeysLine = true;
348                } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) {
349                    wepKeysLine = false;
350                } else if (wepKeysLine) {
351                    line = line.replaceAll(
352                            WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
353                }
354                sb.append(line).append("\n");
355            }
356        } catch (UnsupportedEncodingException e) {
357            return "";
358        }
359        return sb.toString();
360    }
361
362    /**
363     * Restore state from the older supplicant back up data.
364     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
365     *
366     * @param supplicantData Raw byte stream of wpa_supplicant.conf
367     * @param ipConfigData   Raw byte stream of ipconfig.txt
368     * @return list of networks retrieved from the backed up data.
369     */
370    public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
371            byte[] supplicantData, byte[] ipConfigData) {
372        if (supplicantData == null || supplicantData.length == 0) {
373            Log.e(TAG, "Invalid supplicant backup data received");
374            return null;
375        }
376
377        if (mVerboseLoggingEnabled) {
378            mDebugLastSupplicantBackupDataRestored = supplicantData;
379        }
380
381        SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
382                new SupplicantBackupMigration.SupplicantNetworks();
383        // Incorporate the networks present in the backup data.
384        char[] restoredAsChars = new char[supplicantData.length];
385        for (int i = 0; i < supplicantData.length; i++) {
386            restoredAsChars[i] = (char) supplicantData[i];
387        }
388
389        BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
390        supplicantNetworks.readNetworksFromStream(in);
391
392        // Retrieve corresponding WifiConfiguration objects.
393        List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();
394
395        // Now retrieve all the IpConfiguration objects and set in the corresponding
396        // WifiConfiguration objects if ipconfig data is present.
397        if (ipConfigData != null && ipConfigData.length != 0) {
398            SparseArray<IpConfiguration> networks =
399                    IpConfigStore.readIpAndProxyConfigurations(
400                            new ByteArrayInputStream(ipConfigData));
401            if (networks != null) {
402                for (int i = 0; i < networks.size(); i++) {
403                    int id = networks.keyAt(i);
404                    for (WifiConfiguration configuration : configurations) {
405                        // This is a dangerous lookup, but that's how it is currently written.
406                        if (configuration.configKey().hashCode() == id) {
407                            configuration.setIpConfiguration(networks.valueAt(i));
408                        }
409                    }
410                }
411            } else {
412                Log.e(TAG, "Failed to parse ipconfig data");
413            }
414        } else {
415            Log.e(TAG, "Invalid ipconfig backup data received");
416        }
417        return configurations;
418    }
419
420    /**
421     * Enable verbose logging.
422     *
423     * @param verbose verbosity level.
424     */
425    public void enableVerboseLogging(int verbose) {
426        mVerboseLoggingEnabled = (verbose > 0);
427        if (!mVerboseLoggingEnabled) {
428            mDebugLastBackupDataRetrieved = null;
429            mDebugLastBackupDataRestored = null;
430            mDebugLastSupplicantBackupDataRestored = null;
431        }
432    }
433
434    /**
435     * Dump out the last backup/restore data if verbose logging is enabled.
436     *
437     * @param fd   unused
438     * @param pw   PrintWriter for writing dump to
439     * @param args unused
440     */
441    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
442        pw.println("Dump of WifiBackupRestore");
443        if (mDebugLastBackupDataRetrieved != null) {
444            pw.println("Last backup data retrieved: "
445                    + createLogFromBackupData(mDebugLastBackupDataRetrieved));
446        }
447        if (mDebugLastBackupDataRestored != null) {
448            pw.println("Last backup data restored: "
449                    + createLogFromBackupData(mDebugLastBackupDataRestored));
450        }
451        if (mDebugLastSupplicantBackupDataRestored != null) {
452            pw.println("Last old backup data restored: "
453                    + SupplicantBackupMigration.createLogFromBackupData(
454                            mDebugLastSupplicantBackupDataRestored));
455        }
456    }
457
458    /**
459     * These sub classes contain the logic to parse older backups and restore wifi state from it.
460     * Most of the code here has been migrated over from BackupSettingsAgent.
461     * This is kind of ugly text parsing, but it is needed to support the migration of this data.
462     */
463    public static class SupplicantBackupMigration {
464        /**
465         * List of keys to look out for in wpa_supplicant.conf parsing.
466         * These key values are declared in different parts of the wifi codebase today.
467         */
468        public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
469        public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
470        public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.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         */
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 mParsedPskLine;
541            private String[] mParsedWepKeyLines = new String[4];
542            private String mParsedWepTxKeyIdxLine;
543            private String mParsedIdStrLine;
544            public boolean certUsed = false;
545            public boolean isEap = false;
546
547            /**
548             * Read lines from wpa_supplicant.conf stream for this network.
549             */
550            public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
551                final SupplicantNetwork n = new SupplicantNetwork();
552                String line;
553                try {
554                    while (in.ready()) {
555                        line = in.readLine();
556                        if (line == null || line.startsWith("}")) {
557                            break;
558                        }
559                        n.parseLine(line);
560                    }
561                } catch (IOException e) {
562                    return null;
563                }
564                return n;
565            }
566
567            /**
568             * Parse a line from wpa_supplicant.conf stream for this network.
569             */
570            void parseLine(String line) {
571                // Can't rely on particular whitespace patterns so strip leading/trailing.
572                line = line.trim();
573                if (line.isEmpty()) return; // only whitespace; drop the line.
574
575                // Now parse the network block within wpa_supplicant.conf and store the important
576                // lines for procesing later.
577                if (line.startsWith(SUPPLICANT_KEY_SSID)) {
578                    mParsedSSIDLine = line;
579                } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN)) {
580                    mParsedHiddenLine = line;
581                } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT)) {
582                    mParsedKeyMgmtLine = line;
583                    if (line.contains("EAP")) {
584                        isEap = true;
585                    }
586                } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT)) {
587                    certUsed = true;
588                } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT)) {
589                    certUsed = true;
590                } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH)) {
591                    certUsed = true;
592                } else if (line.startsWith(SUPPLICANT_KEY_EAP)) {
593                    isEap = true;
594                } else if (line.startsWith(SUPPLICANT_KEY_PSK)) {
595                    mParsedPskLine = line;
596                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0)) {
597                    mParsedWepKeyLines[0] = line;
598                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1)) {
599                    mParsedWepKeyLines[1] = line;
600                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2)) {
601                    mParsedWepKeyLines[2] = line;
602                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3)) {
603                    mParsedWepKeyLines[3] = line;
604                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX)) {
605                    mParsedWepTxKeyIdxLine = line;
606                } else if (line.startsWith(SUPPLICANT_KEY_ID_STR)) {
607                    mParsedIdStrLine = line;
608                }
609            }
610
611            /**
612             * Create WifiConfiguration object from the parsed data for this network.
613             */
614            public WifiConfiguration createWifiConfiguration() {
615                if (mParsedSSIDLine == null) {
616                    // No SSID => malformed network definition
617                    return null;
618                }
619                WifiConfiguration configuration = new WifiConfiguration();
620                configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1);
621
622                if (mParsedHiddenLine != null) {
623                    // Can't use Boolean.valueOf() because it works only for true/false.
624                    configuration.hiddenSSID =
625                            Integer.parseInt(mParsedHiddenLine.substring(
626                                    mParsedHiddenLine.indexOf('=') + 1)) != 0;
627                }
628                if (mParsedKeyMgmtLine == null) {
629                    // no key_mgmt line specified; this is defined as equivalent to
630                    // "WPA-PSK WPA-EAP".
631                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
632                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
633                } else {
634                    // Need to parse the mParsedKeyMgmtLine line
635                    final String bareKeyMgmt =
636                            mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1);
637                    String[] typeStrings = bareKeyMgmt.split("\\s+");
638
639                    // Parse out all the key management regimes permitted for this network.
640                    // The literal strings here are the standard values permitted in
641                    // wpa_supplicant.conf.
642                    for (int i = 0; i < typeStrings.length; i++) {
643                        final String ktype = typeStrings[i];
644                        if (ktype.equals("NONE")) {
645                            configuration.allowedKeyManagement.set(
646                                    WifiConfiguration.KeyMgmt.NONE);
647                        } else if (ktype.equals("WPA-PSK")) {
648                            configuration.allowedKeyManagement.set(
649                                    WifiConfiguration.KeyMgmt.WPA_PSK);
650                        } else if (ktype.equals("WPA-EAP")) {
651                            configuration.allowedKeyManagement.set(
652                                    WifiConfiguration.KeyMgmt.WPA_EAP);
653                        } else if (ktype.equals("IEEE8021X")) {
654                            configuration.allowedKeyManagement.set(
655                                    WifiConfiguration.KeyMgmt.IEEE8021X);
656                        }
657                    }
658                }
659                if (mParsedPskLine != null) {
660                    configuration.preSharedKey =
661                            mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
662                }
663                if (mParsedWepKeyLines[0] != null) {
664                    configuration.wepKeys[0] =
665                            mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1);
666                }
667                if (mParsedWepKeyLines[1] != null) {
668                    configuration.wepKeys[1] =
669                            mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1);
670                }
671                if (mParsedWepKeyLines[2] != null) {
672                    configuration.wepKeys[2] =
673                            mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1);
674                }
675                if (mParsedWepKeyLines[3] != null) {
676                    configuration.wepKeys[3] =
677                            mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1);
678                }
679                if (mParsedWepTxKeyIdxLine != null) {
680                    configuration.wepTxKeyIndex =
681                            Integer.valueOf(mParsedWepTxKeyIdxLine.substring(
682                                    mParsedWepTxKeyIdxLine.indexOf('=') + 1));
683                }
684                if (mParsedIdStrLine != null) {
685                    String idString =
686                            mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1);
687                    if (idString != null) {
688                        Map<String, String> extras =
689                                SupplicantStaNetworkHal.parseNetworkExtra(
690                                        NativeUtil.removeEnclosingQuotes(idString));
691                        String configKey = extras.get(
692                                SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
693                        if (!configKey.equals(configuration.configKey())) {
694                            // ConfigKey mismatches are expected for private networks because the
695                            // UID is not preserved across backup/restore.
696                            Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey
697                                    + ", Calculated: " + configuration.configKey());
698                        }
699                        // For wpa_supplicant backup data, parse out the creatorUid to ensure that
700                        // these networks were created by system apps.
701                        int creatorUid =
702                                Integer.parseInt(extras.get(
703                                        SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID));
704                        if (creatorUid >= Process.FIRST_APPLICATION_UID) {
705                            return null;
706                        }
707                    }
708                }
709                return configuration;
710            }
711        }
712
713        /**
714         * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
715         * blocks and eliminating duplicates
716         */
717        static class SupplicantNetworks {
718            final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);
719
720            /**
721             * Parse the wpa_supplicant.conf file stream and add networks.
722             */
723            public void readNetworksFromStream(BufferedReader in) {
724                try {
725                    String line;
726                    while (in.ready()) {
727                        line = in.readLine();
728                        if (line != null) {
729                            if (line.startsWith("network")) {
730                                SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);
731                                // Networks that use certificates for authentication can't be
732                                // restored because the certificates they need don't get restored
733                                // (because they are stored in keystore, and can't be restored).
734                                // Similarly, omit EAP network definitions to avoid propagating
735                                // controlled enterprise network definitions.
736                                if (net.isEap || net.certUsed) {
737                                    Log.d(TAG, "Skipping enterprise network for restore: "
738                                            + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine);
739                                    continue;
740                                }
741                                mNetworks.add(net);
742                            }
743                        }
744                    }
745                } catch (IOException e) {
746                    // whatever happened, we're done now
747                }
748            }
749
750            /**
751             * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
752             */
753            public List<WifiConfiguration> retrieveWifiConfigurations() {
754                ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
755                for (SupplicantNetwork net : mNetworks) {
756                    WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
757                    if (wifiConfiguration != null) {
758                        Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
759                        wifiConfigurations.add(wifiConfiguration);
760                    }
761                }
762                return wifiConfigurations;
763            }
764        }
765    }
766}
767