WifiConfigurationUtil.java revision e97e21f1f14e4ef16dd5d675b17615aef3a80301
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.content.pm.UserInfo;
20import android.net.IpConfiguration;
21import android.net.StaticIpConfiguration;
22import android.net.wifi.WifiConfiguration;
23import android.net.wifi.WifiEnterpriseConfig;
24import android.net.wifi.WifiScanner;
25import android.os.UserHandle;
26import android.text.TextUtils;
27import android.util.Log;
28
29import com.android.internal.annotations.VisibleForTesting;
30import com.android.server.wifi.util.NativeUtil;
31
32import java.security.cert.X509Certificate;
33import java.util.Arrays;
34import java.util.BitSet;
35import java.util.Comparator;
36import java.util.List;
37import java.util.Objects;
38
39/**
40 * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
41 * Currently contains:
42 *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
43 *   > Helper methods to identify the encryption of a WifiConfiguration object.
44 */
45public class WifiConfigurationUtil {
46    private static final String TAG = "WifiConfigurationUtil";
47
48    /**
49     * Constants used for validating external config objects.
50     */
51    private static final int ENCLOSING_QUTOES_LEN = 2;
52    private static final int SSID_ASCII_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN;
53    private static final int SSID_ASCII_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN;
54    private static final int SSID_HEX_MIN_LEN = 2;
55    private static final int SSID_HEX_MAX_LEN = 64;
56    private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN;
57    private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN;
58    private static final int PSK_HEX_LEN = 64;
59    @VisibleForTesting
60    public static final String PASSWORD_MASK = "*";
61
62    /**
63     * Check whether a network configuration is visible to a user or any of its managed profiles.
64     *
65     * @param config   the network configuration whose visibility should be checked
66     * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
67     *                 via {@link android.os.UserManager#getProfiles})
68     * @return whether the network configuration is visible to the user or any of its managed
69     * profiles
70     */
71    public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) {
72        return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles));
73    }
74
75    /**
76     * Check whether a uid belong to a user or any of its managed profiles.
77     *
78     * @param uid      uid of the app.
79     * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
80     *                 via {@link android.os.UserManager#getProfiles})
81     * @return whether the uid belongs to the user or any of its managed profiles.
82     */
83    public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) {
84        final int userId = UserHandle.getUserId(uid);
85        for (UserInfo profile : profiles) {
86            if (profile.id == userId) {
87                return true;
88            }
89        }
90        return false;
91    }
92
93    /**
94     * Checks if the provided |wepKeys| array contains any non-null value;
95     */
96    public static boolean hasAnyValidWepKey(String[] wepKeys) {
97        for (int i = 0; i < wepKeys.length; i++) {
98            if (wepKeys[i] != null) {
99                return true;
100            }
101        }
102        return false;
103    }
104
105    /**
106     * Helper method to check if the provided |config| corresponds to a PSK network or not.
107     */
108    public static boolean isConfigForPskNetwork(WifiConfiguration config) {
109        return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
110    }
111
112    /**
113     * Helper method to check if the provided |config| corresponds to a EAP network or not.
114     */
115    public static boolean isConfigForEapNetwork(WifiConfiguration config) {
116        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
117                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
118    }
119
120    /**
121     * Helper method to check if the provided |config| corresponds to a WEP network or not.
122     */
123    public static boolean isConfigForWepNetwork(WifiConfiguration config) {
124        return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
125                && hasAnyValidWepKey(config.wepKeys));
126    }
127
128    /**
129     * Helper method to check if the provided |config| corresponds to an open network or not.
130     */
131    public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
132        return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
133                || isConfigForEapNetwork(config));
134    }
135
136    /**
137     * Compare existing and new WifiConfiguration objects after a network update and return if
138     * IP parameters have changed or not.
139     *
140     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
141     * @param newConfig      New WifiConfiguration object corresponding to the network.
142     * @return true if IP parameters have changed, false otherwise.
143     */
144    public static boolean hasIpChanged(WifiConfiguration existingConfig,
145            WifiConfiguration newConfig) {
146        if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
147            return true;
148        }
149        if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
150            return !Objects.equals(existingConfig.getStaticIpConfiguration(),
151                    newConfig.getStaticIpConfiguration());
152        }
153        return false;
154    }
155
156    /**
157     * Compare existing and new WifiConfiguration objects after a network update and return if
158     * proxy parameters have changed or not.
159     *
160     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
161     * @param newConfig      New WifiConfiguration object corresponding to the network.
162     * @return true if proxy parameters have changed, false if no existing config and proxy settings
163     * are NONE, false otherwise.
164     */
165    public static boolean hasProxyChanged(WifiConfiguration existingConfig,
166            WifiConfiguration newConfig) {
167        if (existingConfig == null) {
168            return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
169        }
170        if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
171            return true;
172        }
173        return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
174    }
175
176    /**
177     * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
178     * credential parameters have changed or not.
179     *
180     * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
181     *                                 network.
182     * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
183     * @return true if credentials have changed, false otherwise.
184     */
185    @VisibleForTesting
186    public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
187            WifiEnterpriseConfig newEnterpriseConfig) {
188        if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
189            if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
190                return true;
191            }
192            if (existingEnterpriseConfig.getPhase2Method()
193                    != newEnterpriseConfig.getPhase2Method()) {
194                return true;
195            }
196            if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(),
197                                  newEnterpriseConfig.getIdentity())
198                    || !TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
199                                         newEnterpriseConfig.getAnonymousIdentity())) {
200                return true;
201            }
202            X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
203            X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
204            if (!Arrays.equals(existingCaCerts, newCaCerts)) {
205                return true;
206            }
207        } else {
208            // One of the configs may have an enterpriseConfig
209            if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
210                return true;
211            }
212        }
213        return false;
214    }
215
216    /**
217     * Compare existing and new WifiConfiguration objects after a network update and return if
218     * credential parameters have changed or not.
219     *
220     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
221     * @param newConfig      New WifiConfiguration object corresponding to the network.
222     * @return true if credentials have changed, false otherwise.
223     */
224    public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
225            WifiConfiguration newConfig) {
226        if (!Objects.equals(existingConfig.allowedKeyManagement,
227                newConfig.allowedKeyManagement)) {
228            return true;
229        }
230        if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
231            return true;
232        }
233        if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
234                newConfig.allowedAuthAlgorithms)) {
235            return true;
236        }
237        if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
238                newConfig.allowedPairwiseCiphers)) {
239            return true;
240        }
241        if (!Objects.equals(existingConfig.allowedGroupCiphers,
242                newConfig.allowedGroupCiphers)) {
243            return true;
244        }
245        if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
246            return true;
247        }
248        if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
249            return true;
250        }
251        if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
252            return true;
253        }
254        if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
255            return true;
256        }
257        if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
258                newConfig.enterpriseConfig)) {
259            return true;
260        }
261        return false;
262    }
263
264    private static boolean validateSsid(String ssid, boolean isAdd) {
265        if (isAdd) {
266            if (ssid == null) {
267                Log.e(TAG, "validateSsid : null string");
268                return false;
269            }
270        } else {
271            if (ssid == null) {
272                // This is an update, so the SSID can be null if that is not being changed.
273                return true;
274            }
275        }
276        if (ssid.isEmpty()) {
277            Log.e(TAG, "validateSsid failed: empty string");
278            return false;
279        }
280        if (ssid.startsWith("\"")) {
281            // ASCII SSID string
282            if (ssid.length() < SSID_ASCII_MIN_LEN) {
283                Log.e(TAG, "validateSsid failed: ascii string size too small: " + ssid.length());
284                return false;
285            }
286            if (ssid.length() > SSID_ASCII_MAX_LEN) {
287                Log.e(TAG, "validateSsid failed: ascii string size too large: " + ssid.length());
288                return false;
289            }
290        } else {
291            // HEX SSID string
292            if (ssid.length() < SSID_HEX_MIN_LEN) {
293                Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length());
294                return false;
295            }
296            if (ssid.length() > SSID_HEX_MAX_LEN) {
297                Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length());
298                return false;
299            }
300        }
301        try {
302            NativeUtil.decodeSsid(ssid);
303        } catch (IllegalArgumentException e) {
304            Log.e(TAG, "validateSsid failed: malformed string: " + ssid);
305            return false;
306        }
307        return true;
308    }
309
310    private static boolean validatePsk(String psk, boolean isAdd) {
311        if (isAdd) {
312            if (psk == null) {
313                Log.e(TAG, "validatePsk: null string");
314                return false;
315            }
316        } else {
317            if (psk == null) {
318                // This is an update, so the psk can be null if that is not being changed.
319                return true;
320            } else if (psk.equals(PASSWORD_MASK)) {
321                // This is an update, so the app might have returned back the masked password, let
322                // it thru. WifiConfigManager will handle it.
323                return true;
324            }
325        }
326        if (psk.isEmpty()) {
327            Log.e(TAG, "validatePsk failed: empty string");
328            return false;
329        }
330        if (psk.startsWith("\"")) {
331            // ASCII PSK string
332            if (psk.length() < PSK_ASCII_MIN_LEN) {
333                Log.e(TAG, "validatePsk failed: ascii string size too small: " + psk.length());
334                return false;
335            }
336            if (psk.length() > PSK_ASCII_MAX_LEN) {
337                Log.e(TAG, "validatePsk failed: ascii string size too large: " + psk.length());
338                return false;
339            }
340        } else {
341            // HEX PSK string
342            if (psk.length() != PSK_HEX_LEN) {
343                Log.e(TAG, "validatePsk failed: hex string size mismatch: " + psk.length());
344                return false;
345            }
346        }
347        try {
348            NativeUtil.hexOrQuotedAsciiStringToBytes(psk);
349        } catch (IllegalArgumentException e) {
350            Log.e(TAG, "validatePsk failed: malformed string: " + psk);
351            return false;
352        }
353        return true;
354    }
355
356    private static boolean validateKeyMgmt(BitSet keyMgmnt) {
357        if (keyMgmnt == null) {
358            Log.e(TAG, "validateKeyMgmt failed: null bitset");
359            return false;
360        }
361        if (keyMgmnt.cardinality() > 1) {
362            if (keyMgmnt.cardinality() != 2) {
363                Log.e(TAG, "validateKeyMgmt failed: cardinality != 2");
364                return false;
365            }
366            if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
367                Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP");
368                return false;
369            }
370            if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
371                    && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
372                Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
373                return false;
374            }
375        }
376        return true;
377    }
378
379    private static boolean validateIpConfiguration(IpConfiguration ipConfig) {
380        if (ipConfig == null) {
381            Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration");
382            return false;
383        }
384        if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
385            StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration();
386            if (staticIpConfig == null) {
387                Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration");
388                return false;
389            }
390            if (staticIpConfig.ipAddress == null) {
391                Log.e(TAG, "validateIpConfiguration failed: null static ip Address");
392                return false;
393            }
394        }
395        return true;
396    }
397
398    /**
399     * Enums to specify if the provided config is being validated for add or update.
400     */
401    public static final boolean VALIDATE_FOR_ADD = true;
402    public static final boolean VALIDATE_FOR_UPDATE = false;
403
404    /**
405     * Validate the configuration received from an external application.
406     *
407     * This method checks for the following parameters:
408     * 1. {@link WifiConfiguration#SSID}
409     * 2. {@link WifiConfiguration#preSharedKey}
410     * 3. {@link WifiConfiguration#allowedKeyManagement}
411     * 4. {@link WifiConfiguration#getIpConfiguration()}
412     *
413     * @param config {@link WifiConfiguration} received from an external application.
414     * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add,
415     *              {@link #VALIDATE_FOR_UPDATE} for a network config received for an update.
416     *              These 2 cases need to be handled differently because the config received for an
417     *              update could contain only the fields that are being changed.
418     * @return true if the parameters are valid, false otherwise.
419     */
420    public static boolean validate(WifiConfiguration config, boolean isAdd) {
421        if (!validateSsid(config.SSID, isAdd)) {
422            return false;
423        }
424        if (!validateKeyMgmt(config.allowedKeyManagement)) {
425            return false;
426        }
427        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
428                && !validatePsk(config.preSharedKey, isAdd)) {
429            return false;
430        }
431        if (!validateIpConfiguration(config.getIpConfiguration())) {
432            return false;
433        }
434        // TBD: Validate some enterprise params as well in the future here.
435        return true;
436    }
437
438    /**
439     * Check if the provided two networks are the same.
440     *
441     * @param config      Configuration corresponding to a network.
442     * @param config1      Configuration corresponding to another network.
443     *
444     * @return true if |config| and |config1| are the same network.
445     *         false otherwise.
446     */
447    public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
448        if (config == null && config1 == null) {
449            return true;
450        }
451        if (config == null || config1 == null) {
452            return false;
453        }
454        if (config.networkId != config1.networkId) {
455            return false;
456        }
457        if (!Objects.equals(config.SSID, config1.SSID)) {
458            return false;
459        }
460        String networkSelectionBSSID = config.getNetworkSelectionStatus()
461                .getNetworkSelectionBSSID();
462        String networkSelectionBSSID1 = config1.getNetworkSelectionStatus()
463                .getNetworkSelectionBSSID();
464        if (!Objects.equals(networkSelectionBSSID, networkSelectionBSSID1)) {
465            return false;
466        }
467        if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
468            return false;
469        }
470        return true;
471    }
472
473    /**
474     * Create a PnoNetwork object from the provided WifiConfiguration.
475     *
476     * @param config      Configuration corresponding to the network.
477     * @param newPriority New priority to be assigned to the network.
478     * @return PnoNetwork object corresponding to the network.
479     */
480    public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
481            WifiConfiguration config, int newPriority) {
482        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
483                new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
484        if (config.hiddenSSID) {
485            pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
486        }
487        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
488        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
489        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
490            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
491        } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
492                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
493            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
494        } else {
495            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
496        }
497        return pnoNetwork;
498    }
499
500
501    /**
502     * General WifiConfiguration list sorting algorithm:
503     * 1, Place the fully enabled networks first.
504     * 2. Next place all the temporarily disabled networks.
505     * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
506     * before WifiConfigManager uses this comparator today!).
507     *
508     * Among the networks with the same status, sort them in the order determined by the return of
509     * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
510     * implementation.
511     */
512    public abstract static class WifiConfigurationComparator implements
513            Comparator<WifiConfiguration> {
514        private static final int ENABLED_NETWORK_SCORE = 3;
515        private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
516        private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
517
518        @Override
519        public int compare(WifiConfiguration a, WifiConfiguration b) {
520            int configAScore = getNetworkStatusScore(a);
521            int configBScore = getNetworkStatusScore(b);
522            if (configAScore == configBScore) {
523                return compareNetworksWithSameStatus(a, b);
524            } else {
525                return Integer.compare(configBScore, configAScore);
526            }
527        }
528
529        // This needs to be implemented by the connected/disconnected PNO list comparator.
530        abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
531
532        /**
533         * Returns an integer representing a score for each configuration. The scores are assigned
534         * based on the status of the configuration. The scores are assigned according to the order:
535         * Fully enabled network > Temporarily disabled network > Permanently disabled network.
536         */
537        private int getNetworkStatusScore(WifiConfiguration config) {
538            if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
539                return ENABLED_NETWORK_SCORE;
540            } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
541                return TEMPORARY_DISABLED_NETWORK_SCORE;
542            } else {
543                return PERMANENTLY_DISABLED_NETWORK_SCORE;
544            }
545        }
546    }
547}
548