WifiConfigStore.java revision eaea4b75c2ecfd192d5dcefcf67bb851bf031f5e
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.IpAssignment;
20import android.net.IpConfiguration.ProxySettings;
21import android.net.wifi.WifiConfiguration;
22import android.net.wifi.WifiConfiguration.Status;
23import android.net.wifi.WifiEnterpriseConfig;
24import android.net.wifi.WifiSsid;
25import android.net.wifi.WpsInfo;
26import android.net.wifi.WpsResult;
27import android.os.FileObserver;
28import android.os.Process;
29import android.security.Credentials;
30import android.security.KeyChain;
31import android.security.KeyStore;
32import android.text.TextUtils;
33import android.util.ArraySet;
34import android.util.LocalLog;
35import android.util.Log;
36import android.util.SparseArray;
37
38import com.android.server.wifi.hotspot2.Utils;
39
40import org.json.JSONException;
41import org.json.JSONObject;
42
43import java.io.BufferedReader;
44import java.io.File;
45import java.io.FileNotFoundException;
46import java.io.FileReader;
47import java.io.IOException;
48import java.net.URLDecoder;
49import java.nio.charset.StandardCharsets;
50import java.security.PrivateKey;
51import java.security.cert.Certificate;
52import java.security.cert.CertificateException;
53import java.security.cert.X509Certificate;
54import java.util.ArrayList;
55import java.util.Arrays;
56import java.util.BitSet;
57import java.util.Collection;
58import java.util.HashMap;
59import java.util.HashSet;
60import java.util.List;
61import java.util.Map;
62import java.util.Set;
63import java.util.zip.CRC32;
64import java.util.zip.Checksum;
65
66/**
67 * This class provides the API's to save/load/modify network configurations from a persistent
68 * config database.
69 * We use wpa_supplicant as our config database currently, but will be migrating to a different
70 * one sometime in the future.
71 * We use keystore for certificate/key management operations.
72 *
73 * NOTE: This class should only be used from WifiConfigManager!!!
74 */
75public class WifiConfigStore {
76
77    public static final String TAG = "WifiConfigStore";
78    // This is the only variable whose contents will not be interpreted by wpa_supplicant. We use it
79    // to store metadata that allows us to correlate a wpa_supplicant.conf entry with additional
80    // information about the same network stored in other files. The metadata is stored as a
81    // serialized JSON dictionary.
82    public static final String ID_STRING_VAR_NAME = "id_str";
83    public static final String ID_STRING_KEY_FQDN = "fqdn";
84    public static final String ID_STRING_KEY_CREATOR_UID = "creatorUid";
85    public static final String ID_STRING_KEY_CONFIG_KEY = "configKey";
86    /**
87     * In old configurations, the "private_key" field was used. However, newer
88     * configurations use the key_id field with the engine_id set to "keystore".
89     * If this field is found in the configuration, the migration code is
90     * triggered.
91     */
92    public static final String OLD_PRIVATE_KEY_NAME = "private_key";
93    /**
94     * This represents an empty value of an enterprise field.
95     * NULL is used at wpa_supplicant to indicate an empty value
96     */
97    public static final String EMPTY_VALUE = "NULL";
98
99    public static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
100    public static final String SUPPLICANT_CONFIG_FILE_BACKUP = SUPPLICANT_CONFIG_FILE + ".tmp";
101
102    private static final boolean DBG = true;
103    private static boolean VDBG = false;
104
105    private final LocalLog mLocalLog;
106    private final WpaConfigFileObserver mFileObserver;
107    private final WifiNative mWifiNative;
108    private final KeyStore mKeyStore;
109    private final boolean mShowNetworks;
110    private final HashSet<String> mBssidBlacklist = new HashSet<String>();
111
112    private final BackupManagerProxy mBackupManagerProxy;
113
114    WifiConfigStore(WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
115            boolean showNetworks, boolean verboseDebug) {
116        mWifiNative = wifiNative;
117        mKeyStore = keyStore;
118        mShowNetworks = showNetworks;
119        mBackupManagerProxy = new BackupManagerProxy();
120
121        if (mShowNetworks) {
122            mLocalLog = localLog;
123            mFileObserver = new WpaConfigFileObserver();
124            mFileObserver.startWatching();
125        } else {
126            mLocalLog = null;
127            mFileObserver = null;
128        }
129        VDBG = verboseDebug;
130    }
131
132    private static String removeDoubleQuotes(String string) {
133        int length = string.length();
134        if ((length > 1) && (string.charAt(0) == '"')
135                && (string.charAt(length - 1) == '"')) {
136            return string.substring(1, length - 1);
137        }
138        return string;
139    }
140
141    private static String makeString(BitSet set, String[] strings) {
142        StringBuffer buf = new StringBuffer();
143        int nextSetBit = -1;
144
145        /* Make sure all set bits are in [0, strings.length) to avoid
146         * going out of bounds on strings.  (Shouldn't happen, but...) */
147        set = set.get(0, strings.length);
148
149        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
150            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
151        }
152
153        // remove trailing space
154        if (set.cardinality() > 0) {
155            buf.setLength(buf.length() - 1);
156        }
157
158        return buf.toString();
159    }
160
161    /*
162     * Convert string to Hexadecimal before passing to wifi native layer
163     * In native function "doCommand()" have trouble in converting Unicode character string to UTF8
164     * conversion to hex is required because SSIDs can have space characters in them;
165     * and that can confuses the supplicant because it uses space charaters as delimiters
166     */
167    private static String encodeSSID(String str) {
168        return Utils.toHex(removeDoubleQuotes(str).getBytes(StandardCharsets.UTF_8));
169    }
170
171    // Certificate and private key management for EnterpriseConfig
172    private static boolean needsKeyStore(WifiEnterpriseConfig config) {
173        return (!(config.getClientCertificate() == null && config.getCaCertificate() == null));
174    }
175
176    private static boolean isHardwareBackedKey(PrivateKey key) {
177        return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
178    }
179
180    private static boolean hasHardwareBackedKey(Certificate certificate) {
181        return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
182    }
183
184    private static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
185        java.lang.String client = config.getClientCertificateAlias();
186        if (!TextUtils.isEmpty(client)) {
187            // a valid client certificate is configured
188
189            // BUGBUG: keyStore.get() never returns certBytes; because it is not
190            // taking WIFI_UID as a parameter. It always looks for certificate
191            // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
192            // all certificates need software keystore until we get the get() API
193            // fixed.
194            return true;
195        }
196        return false;
197    }
198
199    private int lookupString(String string, String[] strings) {
200        int size = strings.length;
201
202        string = string.replace('-', '_');
203
204        for (int i = 0; i < size; i++) {
205            if (string.equals(strings[i])) {
206                return i;
207            }
208        }
209        loge("Failed to look-up a string: " + string);
210        return -1;
211    }
212
213    private void readNetworkBitsetVariable(int netId, BitSet variable, String varName,
214            String[] strings) {
215        String value = mWifiNative.getNetworkVariable(netId, varName);
216        if (!TextUtils.isEmpty(value)) {
217            variable.clear();
218            String[] vals = value.split(" ");
219            for (String val : vals) {
220                int index = lookupString(val, strings);
221                if (0 <= index) {
222                    variable.set(index);
223                }
224            }
225        }
226    }
227
228    /**
229     * Migrates the old style TLS config to the new config style. This should only be used
230     * when restoring an old wpa_supplicant.conf or upgrading from a previous
231     * platform version.
232     *
233     * @return true if the config was updated
234     * @hide
235     */
236    private boolean migrateOldEapTlsNative(WifiEnterpriseConfig config, int netId) {
237        String oldPrivateKey = mWifiNative.getNetworkVariable(netId, OLD_PRIVATE_KEY_NAME);
238        /*
239         * If the old configuration value is not present, then there is nothing
240         * to do.
241         */
242        if (TextUtils.isEmpty(oldPrivateKey)) {
243            return false;
244        } else {
245            // Also ignore it if it's empty quotes.
246            oldPrivateKey = removeDoubleQuotes(oldPrivateKey);
247            if (TextUtils.isEmpty(oldPrivateKey)) {
248                return false;
249            }
250        }
251
252        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, WifiEnterpriseConfig.ENGINE_ENABLE);
253        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY,
254                WifiEnterpriseConfig.ENGINE_ID_KEYSTORE);
255
256        /*
257        * The old key started with the keystore:// URI prefix, but we don't
258        * need that anymore. Trim it off if it exists.
259        */
260        final String keyName;
261        if (oldPrivateKey.startsWith(WifiEnterpriseConfig.KEYSTORE_URI)) {
262            keyName = new String(
263                    oldPrivateKey.substring(WifiEnterpriseConfig.KEYSTORE_URI.length()));
264        } else {
265            keyName = oldPrivateKey;
266        }
267        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, keyName);
268
269        mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_KEY,
270                config.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY, ""));
271
272        mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.ENGINE_ID_KEY,
273                config.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, ""));
274
275        mWifiNative.setNetworkVariable(netId, WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY,
276                config.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, ""));
277
278        // Remove old private_key string so we don't run this again.
279        mWifiNative.setNetworkVariable(netId, OLD_PRIVATE_KEY_NAME, EMPTY_VALUE);
280
281        return true;
282    }
283
284    /**
285     * Migrate certs from global pool to wifi UID if not already done
286     */
287    private void migrateCerts(WifiEnterpriseConfig config) {
288        String client = config.getClientCertificateAlias();
289        // a valid client certificate is configured
290        if (!TextUtils.isEmpty(client)) {
291            if (!mKeyStore.contains(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID)) {
292                mKeyStore.duplicate(Credentials.USER_PRIVATE_KEY + client, -1,
293                        Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
294                mKeyStore.duplicate(Credentials.USER_CERTIFICATE + client, -1,
295                        Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
296            }
297        }
298
299        String[] aliases = config.getCaCertificateAliases();
300        // a valid ca certificate is configured
301        if (aliases != null) {
302            for (String ca : aliases) {
303                if (!TextUtils.isEmpty(ca)
304                        && !mKeyStore.contains(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID)) {
305                    mKeyStore.duplicate(Credentials.CA_CERTIFICATE + ca, -1,
306                            Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
307                }
308            }
309        }
310    }
311
312    /**
313     * Read the variables from the supplicant daemon that are needed to
314     * fill in the WifiConfiguration object.
315     *
316     * @param config the {@link WifiConfiguration} object to be filled in.
317     */
318    public void readNetworkVariables(WifiConfiguration config) {
319        if (config == null) {
320            return;
321        }
322        if (VDBG) localLog("readNetworkVariables: " + config.networkId);
323        int netId = config.networkId;
324        if (netId < 0) {
325            return;
326        }
327        /*
328         * TODO: maybe should have a native method that takes an array of
329         * variable names and returns an array of values. But we'd still
330         * be doing a round trip to the supplicant daemon for each variable.
331         */
332        String value;
333
334        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
335        if (!TextUtils.isEmpty(value)) {
336            if (value.charAt(0) != '"') {
337                config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
338                //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
339                //supplicant string
340            } else {
341                config.SSID = value;
342            }
343        } else {
344            config.SSID = null;
345        }
346
347        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
348        if (!TextUtils.isEmpty(value)) {
349            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(value);
350        } else {
351            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
352        }
353
354        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
355        config.priority = -1;
356        if (!TextUtils.isEmpty(value)) {
357            try {
358                config.priority = Integer.parseInt(value);
359            } catch (NumberFormatException ignore) {
360            }
361        }
362
363        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
364        config.hiddenSSID = false;
365        if (!TextUtils.isEmpty(value)) {
366            try {
367                config.hiddenSSID = Integer.parseInt(value) != 0;
368            } catch (NumberFormatException ignore) {
369            }
370        }
371
372        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pmfVarName);
373        config.requirePMF = false;
374        if (!TextUtils.isEmpty(value)) {
375            try {
376                config.requirePMF = Integer.parseInt(value) != 0;
377            } catch (NumberFormatException ignore) {
378            }
379        }
380
381        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
382        config.wepTxKeyIndex = -1;
383        if (!TextUtils.isEmpty(value)) {
384            try {
385                config.wepTxKeyIndex = Integer.parseInt(value);
386            } catch (NumberFormatException ignore) {
387            }
388        }
389
390        for (int i = 0; i < 4; i++) {
391            value = mWifiNative.getNetworkVariable(netId,
392                    WifiConfiguration.wepKeyVarNames[i]);
393            if (!TextUtils.isEmpty(value)) {
394                config.wepKeys[i] = value;
395            } else {
396                config.wepKeys[i] = null;
397            }
398        }
399
400        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
401        if (!TextUtils.isEmpty(value)) {
402            config.preSharedKey = value;
403        } else {
404            config.preSharedKey = null;
405        }
406
407        readNetworkBitsetVariable(config.networkId, config.allowedProtocols,
408                WifiConfiguration.Protocol.varName, WifiConfiguration.Protocol.strings);
409
410        readNetworkBitsetVariable(config.networkId, config.allowedKeyManagement,
411                WifiConfiguration.KeyMgmt.varName, WifiConfiguration.KeyMgmt.strings);
412
413        readNetworkBitsetVariable(config.networkId, config.allowedAuthAlgorithms,
414                WifiConfiguration.AuthAlgorithm.varName, WifiConfiguration.AuthAlgorithm.strings);
415
416        readNetworkBitsetVariable(config.networkId, config.allowedPairwiseCiphers,
417                WifiConfiguration.PairwiseCipher.varName, WifiConfiguration.PairwiseCipher.strings);
418
419        readNetworkBitsetVariable(config.networkId, config.allowedGroupCiphers,
420                WifiConfiguration.GroupCipher.varName, WifiConfiguration.GroupCipher.strings);
421
422        if (config.enterpriseConfig == null) {
423            config.enterpriseConfig = new WifiEnterpriseConfig();
424        }
425        config.enterpriseConfig.loadFromSupplicant(new SupplicantLoader(netId));
426
427        if (migrateOldEapTlsNative(config.enterpriseConfig, netId)) {
428            saveConfig();
429        }
430
431        migrateCerts(config.enterpriseConfig);
432    }
433
434    /**
435     * Load all the configured networks from wpa_supplicant.
436     *
437     * @param configs       Map of configuration key to configuration objects corresponding to all
438     *                      the networks.
439     * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
440     * @return Max priority of all the configs.
441     */
442    public int loadNetworks(Map<String, WifiConfiguration> configs,
443            SparseArray<Map<String, String>> networkExtras) {
444        int lastPriority = 0;
445        int last_id = -1;
446        boolean done = false;
447        while (!done) {
448            String listStr = mWifiNative.listNetworks(last_id);
449            if (listStr == null) {
450                return lastPriority;
451            }
452            String[] lines = listStr.split("\n");
453            if (mShowNetworks) {
454                localLog("loadNetworks:  ");
455                for (String net : lines) {
456                    localLog(net);
457                }
458            }
459            // Skip the first line, which is a header
460            for (int i = 1; i < lines.length; i++) {
461                String[] result = lines[i].split("\t");
462                // network-id | ssid | bssid | flags
463                WifiConfiguration config = new WifiConfiguration();
464                try {
465                    config.networkId = Integer.parseInt(result[0]);
466                    last_id = config.networkId;
467                } catch (NumberFormatException e) {
468                    loge("Failed to read network-id '" + result[0] + "'");
469                    continue;
470                }
471                // Ignore the supplicant status, start all networks disabled.
472                config.status = WifiConfiguration.Status.DISABLED;
473                readNetworkVariables(config);
474                // Parse the serialized JSON dictionary in ID_STRING_VAR_NAME once and cache the
475                // result for efficiency.
476                Map<String, String> extras = mWifiNative.getNetworkExtra(config.networkId,
477                        ID_STRING_VAR_NAME);
478                if (extras == null) {
479                    extras = new HashMap<String, String>();
480                    // If ID_STRING_VAR_NAME did not contain a dictionary, assume that it contains
481                    // just a quoted FQDN. This is the legacy format that was used in Marshmallow.
482                    final String fqdn = Utils.unquote(mWifiNative.getNetworkVariable(
483                            config.networkId, ID_STRING_VAR_NAME));
484                    if (fqdn != null) {
485                        extras.put(ID_STRING_KEY_FQDN, fqdn);
486                        config.FQDN = fqdn;
487                        // Mark the configuration as a Hotspot 2.0 network.
488                        config.providerFriendlyName = "";
489                    }
490                }
491                networkExtras.put(config.networkId, extras);
492
493                Checksum csum = new CRC32();
494                if (config.SSID != null) {
495                    csum.update(config.SSID.getBytes(), 0, config.SSID.getBytes().length);
496                    long d = csum.getValue();
497                    /* TODO(rpius)
498                    if (mDeletedSSIDs.contains(d)) {
499                        loge(" got CRC for SSID " + config.SSID + " -> " + d + ", was deleted");
500                    } */
501                }
502                if (config.priority > lastPriority) {
503                    lastPriority = config.priority;
504                }
505                config.setIpAssignment(IpAssignment.DHCP);
506                config.setProxySettings(ProxySettings.NONE);
507                if (!WifiServiceImpl.isValid(config)) {
508                    if (mShowNetworks) {
509                        localLog("Ignoring network " + config.networkId + " because configuration "
510                                + "loaded from wpa_supplicant.conf is not valid.");
511                    }
512                    continue;
513                }
514                // The configKey is explicitly stored in wpa_supplicant.conf, because config does
515                // not contain sufficient information to compute it at this point.
516                String configKey = extras.get(ID_STRING_KEY_CONFIG_KEY);
517                if (configKey == null) {
518                    // Handle the legacy case where the configKey is not stored in
519                    // wpa_supplicant.conf but can be computed straight away.
520                    // Force an update of this legacy network configuration by writing
521                    // the configKey for this network into wpa_supplicant.conf.
522                    configKey = config.configKey();
523                    saveNetworkMetadata(config);
524                }
525                final WifiConfiguration duplicateConfig = configs.put(configKey, config);
526                if (duplicateConfig != null) {
527                    // The network is already known. Overwrite the duplicate entry.
528                    if (mShowNetworks) {
529                        localLog("Replacing duplicate network " + duplicateConfig.networkId
530                                + " with " + config.networkId + ".");
531                    }
532                    // This can happen after the user manually connected to an AP and tried to use
533                    // WPS to connect the AP later. In this case, the supplicant will create a new
534                    // network for the AP although there is an existing network already.
535                    mWifiNative.removeNetwork(duplicateConfig.networkId);
536                }
537            }
538            done = (lines.length == 1);
539        }
540        return lastPriority;
541    }
542
543    /**
544     * Install keys for given enterprise network.
545     *
546     * @param existingConfig Existing config corresponding to the network already stored in our
547     *                       database. This maybe null if it's a new network.
548     * @param config         Config corresponding to the network.
549     * @return true if successful, false otherwise.
550     */
551    private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
552            String name) {
553        boolean ret = true;
554        String privKeyName = Credentials.USER_PRIVATE_KEY + name;
555        String userCertName = Credentials.USER_CERTIFICATE + name;
556        if (config.getClientCertificate() != null) {
557            byte[] privKeyData = config.getClientPrivateKey().getEncoded();
558            if (DBG) {
559                if (isHardwareBackedKey(config.getClientPrivateKey())) {
560                    Log.d(TAG, "importing keys " + name + " in hardware backed store");
561                } else {
562                    Log.d(TAG, "importing keys " + name + " in software backed store");
563                }
564            }
565            ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
566                    KeyStore.FLAG_NONE);
567
568            if (!ret) {
569                return ret;
570            }
571
572            ret = putCertInKeyStore(userCertName, config.getClientCertificate());
573            if (!ret) {
574                // Remove private key installed
575                mKeyStore.delete(privKeyName, Process.WIFI_UID);
576                return ret;
577            }
578        }
579
580        X509Certificate[] caCertificates = config.getCaCertificates();
581        Set<String> oldCaCertificatesToRemove = new ArraySet<String>();
582        if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
583            oldCaCertificatesToRemove.addAll(
584                    Arrays.asList(existingConfig.getCaCertificateAliases()));
585        }
586        List<String> caCertificateAliases = null;
587        if (caCertificates != null) {
588            caCertificateAliases = new ArrayList<String>();
589            for (int i = 0; i < caCertificates.length; i++) {
590                String alias = caCertificates.length == 1 ? name
591                        : String.format("%s_%d", name, i);
592
593                oldCaCertificatesToRemove.remove(alias);
594                ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
595                if (!ret) {
596                    // Remove client key+cert
597                    if (config.getClientCertificate() != null) {
598                        mKeyStore.delete(privKeyName, Process.WIFI_UID);
599                        mKeyStore.delete(userCertName, Process.WIFI_UID);
600                    }
601                    // Remove added CA certs.
602                    for (String addedAlias : caCertificateAliases) {
603                        mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
604                    }
605                    return ret;
606                } else {
607                    caCertificateAliases.add(alias);
608                }
609            }
610        }
611        // Remove old CA certs.
612        for (String oldAlias : oldCaCertificatesToRemove) {
613            mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
614        }
615        // Set alias names
616        if (config.getClientCertificate() != null) {
617            config.setClientCertificateAlias(name);
618            config.resetClientKeyEntry();
619        }
620
621        if (caCertificates != null) {
622            config.setCaCertificateAliases(
623                    caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
624            config.resetCaCertificate();
625        }
626        return ret;
627    }
628
629    private boolean putCertInKeyStore(String name, Certificate cert) {
630        try {
631            byte[] certData = Credentials.convertToPem(cert);
632            if (DBG) Log.d(TAG, "putting certificate " + name + " in keystore");
633            return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
634
635        } catch (IOException e1) {
636            return false;
637        } catch (CertificateException e2) {
638            return false;
639        }
640    }
641
642    /**
643     * Remove enterprise keys from the network config.
644     *
645     * @param config Config corresponding to the network.
646     */
647    private void removeKeys(WifiEnterpriseConfig config) {
648        String client = config.getClientCertificateAlias();
649        // a valid client certificate is configured
650        if (!TextUtils.isEmpty(client)) {
651            if (DBG) Log.d(TAG, "removing client private key and user cert");
652            mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
653            mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
654        }
655
656        String[] aliases = config.getCaCertificateAliases();
657        // a valid ca certificate is configured
658        if (aliases != null) {
659            for (String ca : aliases) {
660                if (!TextUtils.isEmpty(ca)) {
661                    if (DBG) Log.d(TAG, "removing CA cert: " + ca);
662                    mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
663                }
664            }
665        }
666    }
667
668    /**
669     * Update the network metadata info stored in wpa_supplicant network extra field.
670     * @param config Config corresponding to the network.
671     * @return true if successful, false otherwise.
672     */
673    public boolean saveNetworkMetadata(WifiConfiguration config) {
674        final Map<String, String> metadata = new HashMap<String, String>();
675        if (config.isPasspoint()) {
676            metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
677        }
678        metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
679        metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
680        if (!mWifiNative.setNetworkExtra(config.networkId, ID_STRING_VAR_NAME, metadata)) {
681            loge("failed to set id_str: " + metadata.toString());
682            return false;
683        }
684        return true;
685    }
686
687    /**
688     * Save an entire network configuration to wpa_supplicant.
689     *
690     * @param config Config corresponding to the network.
691     * @param netId  Net Id of the network.
692     * @return true if successful, false otherwise.
693     */
694    private boolean saveNetwork(WifiConfiguration config, int netId) {
695        if (config == null) {
696            return false;
697        }
698        if (VDBG) localLog("saveNetwork: " + netId);
699        if (config.SSID != null && !mWifiNative.setNetworkVariable(
700                netId,
701                WifiConfiguration.ssidVarName,
702                encodeSSID(config.SSID))) {
703            loge("failed to set SSID: " + config.SSID);
704            return false;
705        }
706        if (!saveNetworkMetadata(config)) {
707            return false;
708        }
709        //set selected BSSID to supplicant
710        if (config.getNetworkSelectionStatus().getNetworkSelectionBSSID() != null) {
711            String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
712            if (!mWifiNative.setNetworkVariable(netId, WifiConfiguration.bssidVarName, bssid)) {
713                loge("failed to set BSSID: " + bssid);
714                return false;
715            }
716        }
717        String allowedKeyManagementString =
718                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
719        if (config.allowedKeyManagement.cardinality() != 0 && !mWifiNative.setNetworkVariable(
720                netId,
721                WifiConfiguration.KeyMgmt.varName,
722                allowedKeyManagementString)) {
723            loge("failed to set key_mgmt: " + allowedKeyManagementString);
724            return false;
725        }
726        String allowedProtocolsString =
727                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
728        if (config.allowedProtocols.cardinality() != 0 && !mWifiNative.setNetworkVariable(
729                netId,
730                WifiConfiguration.Protocol.varName,
731                allowedProtocolsString)) {
732            loge("failed to set proto: " + allowedProtocolsString);
733            return false;
734        }
735        String allowedAuthAlgorithmsString =
736                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
737        if (config.allowedAuthAlgorithms.cardinality() != 0 && !mWifiNative.setNetworkVariable(
738                netId,
739                WifiConfiguration.AuthAlgorithm.varName,
740                allowedAuthAlgorithmsString)) {
741            loge("failed to set auth_alg: " + allowedAuthAlgorithmsString);
742            return false;
743        }
744        String allowedPairwiseCiphersString = makeString(config.allowedPairwiseCiphers,
745                WifiConfiguration.PairwiseCipher.strings);
746        if (config.allowedPairwiseCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
747                netId,
748                WifiConfiguration.PairwiseCipher.varName,
749                allowedPairwiseCiphersString)) {
750            loge("failed to set pairwise: " + allowedPairwiseCiphersString);
751            return false;
752        }
753        String allowedGroupCiphersString =
754                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
755        if (config.allowedGroupCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
756                netId,
757                WifiConfiguration.GroupCipher.varName,
758                allowedGroupCiphersString)) {
759            loge("failed to set group: " + allowedGroupCiphersString);
760            return false;
761        }
762        // Prevent client screw-up by passing in a WifiConfiguration we gave it
763        // by preventing "*" as a key.
764        if (config.preSharedKey != null && !config.preSharedKey.equals("*")
765                && !mWifiNative.setNetworkVariable(
766                netId,
767                WifiConfiguration.pskVarName,
768                config.preSharedKey)) {
769            loge("failed to set psk");
770            return false;
771        }
772        boolean hasSetKey = false;
773        if (config.wepKeys != null) {
774            for (int i = 0; i < config.wepKeys.length; i++) {
775                // Prevent client screw-up by passing in a WifiConfiguration we gave it
776                // by preventing "*" as a key.
777                if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
778                    if (!mWifiNative.setNetworkVariable(
779                            netId,
780                            WifiConfiguration.wepKeyVarNames[i],
781                            config.wepKeys[i])) {
782                        loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
783                        return false;
784                    }
785                    hasSetKey = true;
786                }
787            }
788        }
789        if (hasSetKey) {
790            if (!mWifiNative.setNetworkVariable(
791                    netId,
792                    WifiConfiguration.wepTxKeyIdxVarName,
793                    Integer.toString(config.wepTxKeyIndex))) {
794                loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
795                return false;
796            }
797        }
798        if (!mWifiNative.setNetworkVariable(
799                netId,
800                WifiConfiguration.priorityVarName,
801                Integer.toString(config.priority))) {
802            loge(config.SSID + ": failed to set priority: " + config.priority);
803            return false;
804        }
805        if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
806                netId,
807                WifiConfiguration.hiddenSSIDVarName,
808                Integer.toString(config.hiddenSSID ? 1 : 0))) {
809            loge(config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
810            return false;
811        }
812        if (config.requirePMF && !mWifiNative.setNetworkVariable(
813                netId,
814                WifiConfiguration.pmfVarName,
815                "2")) {
816            loge(config.SSID + ": failed to set requirePMF: " + config.requirePMF);
817            return false;
818        }
819        if (config.updateIdentifier != null && !mWifiNative.setNetworkVariable(
820                netId,
821                WifiConfiguration.updateIdentiferVarName,
822                config.updateIdentifier)) {
823            loge(config.SSID + ": failed to set updateIdentifier: " + config.updateIdentifier);
824            return false;
825        }
826        return true;
827    }
828
829    /**
830     * Update/Install keys for given enterprise network.
831     *
832     * @param config         Config corresponding to the network.
833     * @param existingConfig Existing config corresponding to the network already stored in our
834     *                       database. This maybe null if it's a new network.
835     * @return true if successful, false otherwise.
836     */
837    private boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
838        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
839        if (needsKeyStore(enterpriseConfig)) {
840            try {
841                /* config passed may include only fields being updated.
842                 * In order to generate the key id, fetch uninitialized
843                 * fields from the currently tracked configuration
844                 */
845                String keyId = config.getKeyIdForCredentials(existingConfig);
846
847                if (!installKeys(existingConfig != null
848                        ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
849                    loge(config.SSID + ": failed to install keys");
850                    return false;
851                }
852            } catch (IllegalStateException e) {
853                loge(config.SSID + " invalid config for key installation");
854                return false;
855            }
856        }
857        if (!enterpriseConfig.saveToSupplicant(
858                new SupplicantSaver(config.networkId, config.SSID))) {
859            removeKeys(enterpriseConfig);
860            return false;
861        }
862        return true;
863    }
864
865    /**
866     * Add or update a network configuration to wpa_supplicant.
867     *
868     * @param config         Config corresponding to the network.
869     * @param existingConfig Existing config corresponding to the network saved in our database.
870     * @return true if successful, false otherwise.
871     */
872    public boolean addOrUpdateNetwork(WifiConfiguration config, WifiConfiguration existingConfig) {
873        if (config == null) {
874            return false;
875        }
876        if (VDBG) localLog("addOrUpdateNetwork: " + config.networkId);
877        int netId = config.networkId;
878        boolean newNetwork = false;
879        /*
880         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
881         * network configuration. Otherwise, the networkId should
882         * refer to an existing configuration.
883         */
884        if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
885            newNetwork = true;
886            netId = mWifiNative.addNetwork();
887            if (netId < 0) {
888                loge("Failed to add a network!");
889                return false;
890            } else {
891                logi("addOrUpdateNetwork created netId=" + netId);
892            }
893            // Save the new network ID to the config
894            config.networkId = netId;
895        }
896        if (!saveNetwork(config, netId)) {
897            if (newNetwork) {
898                mWifiNative.removeNetwork(netId);
899                loge("Failed to set a network variable, removed network: " + netId);
900            }
901            return false;
902        }
903        if (config.enterpriseConfig != null
904                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
905            return updateNetworkKeys(config, existingConfig);
906        }
907        // Stage the backup of the SettingsProvider package which backs this up
908        mBackupManagerProxy.notifyDataChanged();
909        return true;
910    }
911
912    /**
913     * Remove the specified network and save config
914     *
915     * @param config Config corresponding to the network.
916     * @return {@code true} if it succeeds, {@code false} otherwise
917     */
918    public boolean removeNetwork(WifiConfiguration config) {
919        if (config == null) {
920            return false;
921        }
922        if (VDBG) localLog("removeNetwork: " + config.networkId);
923        if (!mWifiNative.removeNetwork(config.networkId)) {
924            loge("Remove network in wpa_supplicant failed on " + config.networkId);
925            return false;
926        }
927        // Remove any associated keys
928        if (config.enterpriseConfig != null) {
929            removeKeys(config.enterpriseConfig);
930        }
931        // Stage the backup of the SettingsProvider package which backs this up
932        mBackupManagerProxy.notifyDataChanged();
933        return true;
934    }
935
936    /**
937     * Enable a network in wpa_supplicant.
938     *
939     * @param config Config corresponding to the network.
940     * @return true if successful, false otherwise.
941     */
942    public boolean enableNetwork(WifiConfiguration config) {
943        if (config == null) {
944            return false;
945        }
946        if (VDBG) localLog("enableNetwork: " + config.networkId);
947        if (!mWifiNative.enableNetworkWithoutConnect(config.networkId)) {
948            loge("Enable network in wpa_supplicant failed on " + config.networkId);
949            return false;
950        }
951        config.status = Status.ENABLED;
952        return true;
953    }
954
955    /**
956     * Select a network in wpa_supplicant.
957     *
958     * @param config Config corresponding to the network.
959     * @return true if successful, false otherwise.
960     */
961    public boolean selectNetwork(WifiConfiguration config, Collection<WifiConfiguration> configs) {
962        if (config == null) {
963            return false;
964        }
965        if (VDBG) localLog("selectNetwork: " + config.networkId);
966        if (!mWifiNative.selectNetwork(config.networkId)) {
967            loge("Select network in wpa_supplicant failed on " + config.networkId);
968            return false;
969        }
970        config.status = Status.ENABLED;
971        markAllNetworksDisabledExcept(config.networkId, configs);
972        return true;
973    }
974
975    /**
976     * Disable a network in wpa_supplicant.
977     *
978     * @param config Config corresponding to the network.
979     * @return true if successful, false otherwise.
980     */
981    boolean disableNetwork(WifiConfiguration config) {
982        if (config == null) {
983            return false;
984        }
985        if (VDBG) localLog("disableNetwork: " + config.networkId);
986        if (!mWifiNative.disableNetwork(config.networkId)) {
987            loge("Disable network in wpa_supplicant failed on " + config.networkId);
988            return false;
989        }
990        config.status = Status.DISABLED;
991        return true;
992    }
993
994    /**
995     * Set priority for a network in wpa_supplicant.
996     *
997     * @param config Config corresponding to the network.
998     * @return true if successful, false otherwise.
999     */
1000    public boolean setNetworkPriority(WifiConfiguration config, int priority) {
1001        if (config == null) {
1002            return false;
1003        }
1004        if (VDBG) localLog("setNetworkPriority: " + config.networkId);
1005        if (!mWifiNative.setNetworkVariable(config.networkId,
1006                WifiConfiguration.priorityVarName, Integer.toString(priority))) {
1007            loge("Set priority of network in wpa_supplicant failed on " + config.networkId);
1008            return false;
1009        }
1010        config.priority = priority;
1011        return true;
1012    }
1013
1014    /**
1015     * Set SSID for a network in wpa_supplicant.
1016     *
1017     * @param config Config corresponding to the network.
1018     * @return true if successful, false otherwise.
1019     */
1020    public boolean setNetworkSSID(WifiConfiguration config, String ssid) {
1021        if (config == null) {
1022            return false;
1023        }
1024        if (VDBG) localLog("setNetworkSSID: " + config.networkId);
1025        if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.ssidVarName,
1026                encodeSSID(ssid))) {
1027            loge("Set SSID of network in wpa_supplicant failed on " + config.networkId);
1028            return false;
1029        }
1030        config.SSID = ssid;
1031        return true;
1032    }
1033
1034    /**
1035     * Set BSSID for a network in wpa_supplicant from network selection.
1036     *
1037     * @param config Config corresponding to the network.
1038     * @param bssid  BSSID to be set.
1039     * @return true if successful, false otherwise.
1040     */
1041    public boolean setNetworkBSSID(WifiConfiguration config, String bssid) {
1042        // Sanity check the config is valid
1043        if (config == null
1044                || (config.networkId == WifiConfiguration.INVALID_NETWORK_ID
1045                && config.SSID == null)) {
1046            return false;
1047        }
1048        if (VDBG) localLog("setNetworkBSSID: " + config.networkId);
1049        if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.bssidVarName,
1050                bssid)) {
1051            loge("Set BSSID of network in wpa_supplicant failed on " + config.networkId);
1052            return false;
1053        }
1054        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
1055        return true;
1056    }
1057
1058    /**
1059     * Enable/Disable HS20 parameter in wpa_supplicant.
1060     *
1061     * @param enable Enable/Disable the parameter.
1062     */
1063    public void enableHS20(boolean enable) {
1064        mWifiNative.setHs20(enable);
1065    }
1066
1067    /**
1068     * Enables all the networks in the provided list in wpa_supplicant.
1069     *
1070     * @param configs Collection of configs which needs to be enabled.
1071     * @return true if successful, false otherwise.
1072     */
1073    public boolean enableAllNetworks(Collection<WifiConfiguration> configs) {
1074        if (VDBG) localLog("enableAllNetworksNative");
1075        boolean networkEnabled = false;
1076        for (WifiConfiguration config : configs) {
1077            if (config != null && !config.ephemeral
1078                    && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
1079                if (enableNetwork(config)) {
1080                    networkEnabled = true;
1081                }
1082            }
1083        }
1084        saveConfig();
1085        return networkEnabled;
1086    }
1087
1088    /**
1089     * Disables all the networks in the provided list in wpa_supplicant.
1090     *
1091     * @param configs Collection of configs which needs to be enabled.
1092     * @return true if successful, false otherwise.
1093     */
1094    public boolean disableAllNetworks(Collection<WifiConfiguration> configs) {
1095        if (VDBG) localLog("disableAllNetworks");
1096        boolean networkDisabled = false;
1097        for (WifiConfiguration enabled : configs) {
1098            if (disableNetwork(enabled)) {
1099                networkDisabled = true;
1100            }
1101        }
1102        saveConfig();
1103        return networkDisabled;
1104    }
1105
1106    /**
1107     * Save the current configuration to wpa_supplicant.conf.
1108     */
1109    public boolean saveConfig() {
1110        return mWifiNative.saveConfig();
1111    }
1112
1113    /**
1114     * Read network variables from wpa_supplicant.conf.
1115     *
1116     * @param key The parameter to be parsed.
1117     * @return Map of corresponding configKey to the value of the param requested.
1118     */
1119    public Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
1120        Map<String, String> result = new HashMap<>();
1121        BufferedReader reader = null;
1122        try {
1123            reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
1124            result = readNetworkVariablesFromReader(reader, key);
1125        } catch (FileNotFoundException e) {
1126            if (VDBG) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
1127        } catch (IOException e) {
1128            if (VDBG) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
1129        } finally {
1130            try {
1131                if (reader != null) {
1132                    reader.close();
1133                }
1134            } catch (IOException e) {
1135                if (VDBG) {
1136                    loge("Could not close reader for " + SUPPLICANT_CONFIG_FILE + ", " + e);
1137                }
1138            }
1139        }
1140        return result;
1141    }
1142
1143    /**
1144     * Read network variables from a given reader. This method is separate from
1145     * readNetworkVariablesFromSupplicantFile() for testing.
1146     *
1147     * @param reader The reader to read the network variables from.
1148     * @param key The parameter to be parsed.
1149     * @return Map of corresponding configKey to the value of the param requested.
1150     */
1151    public Map<String, String> readNetworkVariablesFromReader(BufferedReader reader, String key)
1152            throws IOException {
1153        Map<String, String> result = new HashMap<>();
1154        if (VDBG) localLog("readNetworkVariablesFromReader key=" + key);
1155        boolean found = false;
1156        String configKey = null;
1157        String value = null;
1158        for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1159            if (line.matches("[ \\t]*network=\\{")) {
1160                found = true;
1161                configKey = null;
1162                value = null;
1163            } else if (line.matches("[ \\t]*\\}")) {
1164                found = false;
1165                configKey = null;
1166                value = null;
1167            }
1168            if (found) {
1169                String trimmedLine = line.trim();
1170                if (trimmedLine.startsWith(ID_STRING_VAR_NAME + "=")) {
1171                    try {
1172                        // Trim the quotes wrapping the id_str value.
1173                        final String encodedExtras = trimmedLine.substring(
1174                                8, trimmedLine.length() -1);
1175                        final JSONObject json =
1176                                new JSONObject(URLDecoder.decode(encodedExtras, "UTF-8"));
1177                        if (json.has(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY)) {
1178                            final Object configKeyFromJson =
1179                                    json.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY);
1180                            if (configKeyFromJson instanceof String) {
1181                                configKey = (String) configKeyFromJson;
1182                            }
1183                        }
1184                    } catch (JSONException e) {
1185                        if (VDBG) {
1186                            loge("Could not get "+ WifiConfigStore.ID_STRING_KEY_CONFIG_KEY
1187                                    + ", " + e);
1188                        }
1189                    }
1190                }
1191                if (trimmedLine.startsWith(key + "=")) {
1192                    value = trimmedLine.substring(key.length() + 1);
1193                }
1194                if (configKey != null && value != null) {
1195                    result.put(configKey, value);
1196                }
1197            }
1198        }
1199        return result;
1200    }
1201
1202    /**
1203     * Checks if the network is a sim config.
1204     *
1205     * @param config Config corresponding to the network.
1206     * @return true if it is a sim config, false otherwise.
1207     */
1208    public boolean isSimConfig(WifiConfiguration config) {
1209        if (config == null) {
1210            return false;
1211        }
1212
1213        if (config.enterpriseConfig == null) {
1214            return false;
1215        }
1216
1217        int method = config.enterpriseConfig.getEapMethod();
1218        return (method == WifiEnterpriseConfig.Eap.SIM
1219                || method == WifiEnterpriseConfig.Eap.AKA
1220                || method == WifiEnterpriseConfig.Eap.AKA_PRIME);
1221    }
1222
1223    /**
1224     * Resets all sim networks from the provided network list.
1225     *
1226     * @param configs List of all the networks.
1227     */
1228    public void resetSimNetworks(Collection<WifiConfiguration> configs) {
1229        if (VDBG) localLog("resetSimNetworks");
1230        for (WifiConfiguration config : configs) {
1231            if (isSimConfig(config)) {
1232                /* This configuration may have cached Pseudonym IDs; lets remove them */
1233                mWifiNative.setNetworkVariable(config.networkId, "identity", "NULL");
1234                mWifiNative.setNetworkVariable(config.networkId, "anonymous_identity", "NULL");
1235            }
1236        }
1237    }
1238
1239    /**
1240     * Clear BSSID blacklist in wpa_supplicant.
1241     */
1242    public void clearBssidBlacklist() {
1243        if (VDBG) localLog("clearBlacklist");
1244        mBssidBlacklist.clear();
1245        mWifiNative.clearBlacklist();
1246        mWifiNative.setBssidBlacklist(null);
1247    }
1248
1249    /**
1250     * Add a BSSID to the blacklist.
1251     *
1252     * @param bssid bssid to be added.
1253     */
1254    public void blackListBssid(String bssid) {
1255        if (bssid == null) {
1256            return;
1257        }
1258        if (VDBG) localLog("blackListBssid: " + bssid);
1259        mBssidBlacklist.add(bssid);
1260        // Blacklist at wpa_supplicant
1261        mWifiNative.addToBlacklist(bssid);
1262        // Blacklist at firmware
1263        String[] list = mBssidBlacklist.toArray(new String[mBssidBlacklist.size()]);
1264        mWifiNative.setBssidBlacklist(list);
1265    }
1266
1267    /**
1268     * Checks if the provided bssid is blacklisted or not.
1269     *
1270     * @param bssid bssid to be checked.
1271     * @return true if present, false otherwise.
1272     */
1273    public boolean isBssidBlacklisted(String bssid) {
1274        return mBssidBlacklist.contains(bssid);
1275    }
1276
1277    /* Mark all networks except specified netId as disabled */
1278    private void markAllNetworksDisabledExcept(int netId, Collection<WifiConfiguration> configs) {
1279        for (WifiConfiguration config : configs) {
1280            if (config != null && config.networkId != netId) {
1281                if (config.status != Status.DISABLED) {
1282                    config.status = Status.DISABLED;
1283                }
1284            }
1285        }
1286    }
1287
1288    private void markAllNetworksDisabled(Collection<WifiConfiguration> configs) {
1289        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, configs);
1290    }
1291
1292    /**
1293     * Start WPS pin method configuration with pin obtained
1294     * from the access point
1295     *
1296     * @param config WPS configuration
1297     * @return Wps result containing status and pin
1298     */
1299    public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config,
1300            Collection<WifiConfiguration> configs) {
1301        WpsResult result = new WpsResult();
1302        if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
1303            /* WPS leaves all networks disabled */
1304            markAllNetworksDisabled(configs);
1305            result.status = WpsResult.Status.SUCCESS;
1306        } else {
1307            loge("Failed to start WPS pin method configuration");
1308            result.status = WpsResult.Status.FAILURE;
1309        }
1310        return result;
1311    }
1312
1313    /**
1314     * Start WPS pin method configuration with obtained
1315     * from the device
1316     *
1317     * @return WpsResult indicating status and pin
1318     */
1319    public WpsResult startWpsWithPinFromDevice(WpsInfo config,
1320            Collection<WifiConfiguration> configs) {
1321        WpsResult result = new WpsResult();
1322        result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
1323        /* WPS leaves all networks disabled */
1324        if (!TextUtils.isEmpty(result.pin)) {
1325            markAllNetworksDisabled(configs);
1326            result.status = WpsResult.Status.SUCCESS;
1327        } else {
1328            loge("Failed to start WPS pin method configuration");
1329            result.status = WpsResult.Status.FAILURE;
1330        }
1331        return result;
1332    }
1333
1334    /**
1335     * Start WPS push button configuration
1336     *
1337     * @param config WPS configuration
1338     * @return WpsResult indicating status and pin
1339     */
1340    public WpsResult startWpsPbc(WpsInfo config,
1341            Collection<WifiConfiguration> configs) {
1342        WpsResult result = new WpsResult();
1343        if (mWifiNative.startWpsPbc(config.BSSID)) {
1344            /* WPS leaves all networks disabled */
1345            markAllNetworksDisabled(configs);
1346            result.status = WpsResult.Status.SUCCESS;
1347        } else {
1348            loge("Failed to start WPS push button configuration");
1349            result.status = WpsResult.Status.FAILURE;
1350        }
1351        return result;
1352    }
1353
1354    protected void logd(String s) {
1355        Log.d(TAG, s);
1356    }
1357
1358    protected void logi(String s) {
1359        Log.i(TAG, s);
1360    }
1361
1362    protected void loge(String s) {
1363        loge(s, false);
1364    }
1365
1366    protected void loge(String s, boolean stack) {
1367        if (stack) {
1368            Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
1369                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
1370                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
1371                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
1372        } else {
1373            Log.e(TAG, s);
1374        }
1375    }
1376
1377    protected void log(String s) {
1378        Log.d(TAG, s);
1379    }
1380
1381    private void localLog(String s) {
1382        if (mLocalLog != null) {
1383            mLocalLog.log(TAG + ": " + s);
1384        }
1385    }
1386
1387    private void localLogAndLogcat(String s) {
1388        localLog(s);
1389        Log.d(TAG, s);
1390    }
1391
1392    private class SupplicantSaver implements WifiEnterpriseConfig.SupplicantSaver {
1393        private final int mNetId;
1394        private final String mSetterSSID;
1395
1396        SupplicantSaver(int netId, String setterSSID) {
1397            mNetId = netId;
1398            mSetterSSID = setterSSID;
1399        }
1400
1401        @Override
1402        public boolean saveValue(String key, String value) {
1403            if (key.equals(WifiEnterpriseConfig.PASSWORD_KEY)
1404                    && value != null && value.equals("*")) {
1405                // No need to try to set an obfuscated password, which will fail
1406                return true;
1407            }
1408            if (key.equals(WifiEnterpriseConfig.REALM_KEY)
1409                    || key.equals(WifiEnterpriseConfig.PLMN_KEY)) {
1410                // No need to save realm or PLMN in supplicant
1411                return true;
1412            }
1413            // TODO: We need a way to clear values in wpa_supplicant as opposed to
1414            // mapping unset values to empty strings.
1415            if (value == null) {
1416                value = "\"\"";
1417            }
1418            if (!mWifiNative.setNetworkVariable(mNetId, key, value)) {
1419                loge(mSetterSSID + ": failed to set " + key + ": " + value);
1420                return false;
1421            }
1422            return true;
1423        }
1424    }
1425
1426    private class SupplicantLoader implements WifiEnterpriseConfig.SupplicantLoader {
1427        private final int mNetId;
1428
1429        SupplicantLoader(int netId) {
1430            mNetId = netId;
1431        }
1432
1433        @Override
1434        public String loadValue(String key) {
1435            String value = mWifiNative.getNetworkVariable(mNetId, key);
1436            if (!TextUtils.isEmpty(value)) {
1437                if (!enterpriseConfigKeyShouldBeQuoted(key)) {
1438                    value = removeDoubleQuotes(value);
1439                }
1440                return value;
1441            } else {
1442                return null;
1443            }
1444        }
1445
1446        /**
1447         * Returns true if a particular config key needs to be quoted when passed to the supplicant.
1448         */
1449        private boolean enterpriseConfigKeyShouldBeQuoted(String key) {
1450            switch (key) {
1451                case WifiEnterpriseConfig.EAP_KEY:
1452                case WifiEnterpriseConfig.ENGINE_KEY:
1453                    return false;
1454                default:
1455                    return true;
1456            }
1457        }
1458    }
1459
1460    // TODO(rpius): Remove this.
1461    private class WpaConfigFileObserver extends FileObserver {
1462
1463        WpaConfigFileObserver() {
1464            super(SUPPLICANT_CONFIG_FILE, CLOSE_WRITE);
1465        }
1466
1467        @Override
1468        public void onEvent(int event, String path) {
1469            if (event == CLOSE_WRITE) {
1470                File file = new File(SUPPLICANT_CONFIG_FILE);
1471                if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
1472            }
1473        }
1474    }
1475}
1476