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