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