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 
17 package com.android.server.wifi;
18 
19 import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
20 
21 import android.content.pm.UserInfo;
22 import android.net.IpConfiguration;
23 import android.net.MacAddress;
24 import android.net.StaticIpConfiguration;
25 import android.net.wifi.WifiConfiguration;
26 import android.net.wifi.WifiEnterpriseConfig;
27 import android.net.wifi.WifiNetworkSpecifier;
28 import android.net.wifi.WifiScanner;
29 import android.os.PatternMatcher;
30 import android.os.UserHandle;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.util.Pair;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.server.wifi.util.NativeUtil;
37 import com.android.server.wifi.util.TelephonyUtil;
38 
39 import java.nio.charset.StandardCharsets;
40 import java.security.cert.X509Certificate;
41 import java.util.Arrays;
42 import java.util.BitSet;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
49  * Currently contains:
50  *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
51  *   > Helper methods to identify the encryption of a WifiConfiguration object.
52  */
53 public class WifiConfigurationUtil {
54     private static final String TAG = "WifiConfigurationUtil";
55 
56     /**
57      * Constants used for validating external config objects.
58      */
59     private static final int ENCLOSING_QUOTES_LEN = 2;
60     private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN;
61     private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUOTES_LEN;
62     private static final int SSID_HEX_MIN_LEN = 2;
63     private static final int SSID_HEX_MAX_LEN = 64;
64     private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUOTES_LEN;
65     private static final int SAE_ASCII_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN;
66     private static final int PSK_SAE_ASCII_MAX_LEN = 63 + ENCLOSING_QUOTES_LEN;
67     private static final int PSK_SAE_HEX_LEN = 64;
68     @VisibleForTesting
69     public static final String PASSWORD_MASK = "*";
70     private static final String MATCH_EMPTY_SSID_PATTERN_PATH = "";
71     private static final Pair<MacAddress, MacAddress> MATCH_NONE_BSSID_PATTERN =
72             new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS);
73     private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN =
74             new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
75 
76     /**
77      * Check whether a network configuration is visible to a user or any of its managed profiles.
78      *
79      * @param config   the network configuration whose visibility should be checked
80      * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
81      *                 via {@link android.os.UserManager#getProfiles})
82      * @return whether the network configuration is visible to the user or any of its managed
83      * profiles
84      */
isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles)85     public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) {
86         return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles));
87     }
88 
89     /**
90      * Check whether a uid belong to a user or any of its managed profiles.
91      *
92      * @param uid      uid of the app.
93      * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
94      *                 via {@link android.os.UserManager#getProfiles})
95      * @return whether the uid belongs to the user or any of its managed profiles.
96      */
doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles)97     public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) {
98         final int userId = UserHandle.getUserId(uid);
99         for (UserInfo profile : profiles) {
100             if (profile.id == userId) {
101                 return true;
102             }
103         }
104         return false;
105     }
106 
107     /**
108      * Checks if the provided |wepKeys| array contains any non-null value;
109      */
hasAnyValidWepKey(String[] wepKeys)110     public static boolean hasAnyValidWepKey(String[] wepKeys) {
111         for (int i = 0; i < wepKeys.length; i++) {
112             if (wepKeys[i] != null) {
113                 return true;
114             }
115         }
116         return false;
117     }
118 
119     /**
120      * Helper method to check if the provided |config| corresponds to a PSK network or not.
121      */
isConfigForPskNetwork(WifiConfiguration config)122     public static boolean isConfigForPskNetwork(WifiConfiguration config) {
123         return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK);
124     }
125 
126     /**
127      * Helper method to check if the provided |config| corresponds to an SAE network or not.
128      */
isConfigForSaeNetwork(WifiConfiguration config)129     public static boolean isConfigForSaeNetwork(WifiConfiguration config) {
130         return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE);
131     }
132 
133     /**
134      * Helper method to check if the provided |config| corresponds to an OWE network or not.
135      */
isConfigForOweNetwork(WifiConfiguration config)136     public static boolean isConfigForOweNetwork(WifiConfiguration config) {
137         return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
138     }
139 
140     /**
141      * Helper method to check if the provided |config| corresponds to a EAP network or not.
142      */
isConfigForEapNetwork(WifiConfiguration config)143     public static boolean isConfigForEapNetwork(WifiConfiguration config) {
144         return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
145                 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
146     }
147 
148     /**
149      * Helper method to check if the provided |config| corresponds to a EAP Suite-B network or not.
150      */
isConfigForEapSuiteBNetwork(WifiConfiguration config)151     public static boolean isConfigForEapSuiteBNetwork(WifiConfiguration config) {
152         return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192);
153     }
154 
155     /**
156      * Helper method to check if the provided |config| corresponds to a WEP network or not.
157      */
isConfigForWepNetwork(WifiConfiguration config)158     public static boolean isConfigForWepNetwork(WifiConfiguration config) {
159         return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
160                 && hasAnyValidWepKey(config.wepKeys));
161     }
162 
163     /**
164      * Helper method to check if the provided |config| corresponds to an open or enhanced
165      * open network, or not.
166      */
isConfigForOpenNetwork(WifiConfiguration config)167     public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
168         return (!(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
169                 || isConfigForEapNetwork(config) || isConfigForSaeNetwork(config)
170                 || isConfigForEapSuiteBNetwork(config)));
171     }
172 
173     /**
174      * Compare existing and new WifiConfiguration objects after a network update and return if
175      * IP parameters have changed or not.
176      *
177      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
178      * @param newConfig      New WifiConfiguration object corresponding to the network.
179      * @return true if IP parameters have changed, false otherwise.
180      */
hasIpChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)181     public static boolean hasIpChanged(WifiConfiguration existingConfig,
182             WifiConfiguration newConfig) {
183         if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
184             return true;
185         }
186         if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
187             return !Objects.equals(existingConfig.getStaticIpConfiguration(),
188                     newConfig.getStaticIpConfiguration());
189         }
190         return false;
191     }
192 
193     /**
194      * Compare existing and new WifiConfiguration objects after a network update and return if
195      * proxy parameters have changed or not.
196      *
197      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
198      * @param newConfig      New WifiConfiguration object corresponding to the network.
199      * @return true if proxy parameters have changed, false if no existing config and proxy settings
200      * are NONE, false otherwise.
201      */
hasProxyChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)202     public static boolean hasProxyChanged(WifiConfiguration existingConfig,
203             WifiConfiguration newConfig) {
204         if (existingConfig == null) {
205             return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
206         }
207         if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
208             return true;
209         }
210         return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
211     }
212 
213     /**
214      * Compare existing and new WifiConfiguration objects after a network update and return if
215      * MAC randomization setting has changed or not.
216      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
217      * @param newConfig      New WifiConfiguration object corresponding to the network.
218      * @return true if MAC randomization setting setting changed or the existing confiuration is
219      * null and the newConfig is setting macRandomizationSetting to the default value.
220      */
hasMacRandomizationSettingsChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)221     public static boolean hasMacRandomizationSettingsChanged(WifiConfiguration existingConfig,
222             WifiConfiguration newConfig) {
223         if (existingConfig == null) {
224             return newConfig.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_PERSISTENT;
225         }
226         return newConfig.macRandomizationSetting != existingConfig.macRandomizationSetting;
227     }
228 
229     /**
230      * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
231      * credential parameters have changed or not.
232      *
233      * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
234      *                                 network.
235      * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
236      * @return true if credentials have changed, false otherwise.
237      */
238     @VisibleForTesting
hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig, WifiEnterpriseConfig newEnterpriseConfig)239     public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
240             WifiEnterpriseConfig newEnterpriseConfig) {
241         if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
242             if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
243                 return true;
244             }
245             if (existingEnterpriseConfig.getPhase2Method()
246                     != newEnterpriseConfig.getPhase2Method()) {
247                 return true;
248             }
249             if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(),
250                                   newEnterpriseConfig.getIdentity())) {
251                 return true;
252             }
253             if (!TelephonyUtil.isSimEapMethod(existingEnterpriseConfig.getEapMethod())
254                     && !TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
255                     newEnterpriseConfig.getAnonymousIdentity())) {
256                 return true;
257             }
258             if (!TextUtils.equals(existingEnterpriseConfig.getPassword(),
259                                     newEnterpriseConfig.getPassword())) {
260                 return true;
261             }
262             X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
263             X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
264             if (!Arrays.equals(existingCaCerts, newCaCerts)) {
265                 return true;
266             }
267         } else {
268             // One of the configs may have an enterpriseConfig
269             if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
270                 return true;
271             }
272         }
273         return false;
274     }
275 
276     /**
277      * Compare existing and new WifiConfiguration objects after a network update and return if
278      * credential parameters have changed or not.
279      *
280      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
281      * @param newConfig      New WifiConfiguration object corresponding to the network.
282      * @return true if credentials have changed, false otherwise.
283      */
hasCredentialChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)284     public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
285             WifiConfiguration newConfig) {
286         if (!Objects.equals(existingConfig.allowedKeyManagement,
287                 newConfig.allowedKeyManagement)) {
288             return true;
289         }
290         if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
291             return true;
292         }
293         if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
294                 newConfig.allowedAuthAlgorithms)) {
295             return true;
296         }
297         if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
298                 newConfig.allowedPairwiseCiphers)) {
299             return true;
300         }
301         if (!Objects.equals(existingConfig.allowedGroupCiphers,
302                 newConfig.allowedGroupCiphers)) {
303             return true;
304         }
305         if (!Objects.equals(existingConfig.allowedGroupManagementCiphers,
306                 newConfig.allowedGroupManagementCiphers)) {
307             return true;
308         }
309         if (!Objects.equals(existingConfig.allowedSuiteBCiphers,
310                 newConfig.allowedSuiteBCiphers)) {
311             return true;
312         }
313         if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
314             return true;
315         }
316         if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
317             return true;
318         }
319         if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
320             return true;
321         }
322         if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
323             return true;
324         }
325         if (existingConfig.requirePMF != newConfig.requirePMF) {
326             return true;
327         }
328         if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
329                 newConfig.enterpriseConfig)) {
330             return true;
331         }
332         return false;
333     }
334 
validateSsid(String ssid, boolean isAdd)335     private static boolean validateSsid(String ssid, boolean isAdd) {
336         if (isAdd) {
337             if (ssid == null) {
338                 Log.e(TAG, "validateSsid : null string");
339                 return false;
340             }
341         } else {
342             if (ssid == null) {
343                 // This is an update, so the SSID can be null if that is not being changed.
344                 return true;
345             }
346         }
347         if (ssid.isEmpty()) {
348             Log.e(TAG, "validateSsid failed: empty string");
349             return false;
350         }
351         if (ssid.startsWith("\"")) {
352             // UTF-8 SSID string
353             byte[] ssidBytes = ssid.getBytes(StandardCharsets.UTF_8);
354             if (ssidBytes.length < SSID_UTF_8_MIN_LEN) {
355                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too small: "
356                         + ssidBytes.length);
357                 return false;
358             }
359             if (ssidBytes.length > SSID_UTF_8_MAX_LEN) {
360                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too large: "
361                         + ssidBytes.length);
362                 return false;
363             }
364         } else {
365             // HEX SSID string
366             if (ssid.length() < SSID_HEX_MIN_LEN) {
367                 Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length());
368                 return false;
369             }
370             if (ssid.length() > SSID_HEX_MAX_LEN) {
371                 Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length());
372                 return false;
373             }
374         }
375         try {
376             NativeUtil.decodeSsid(ssid);
377         } catch (IllegalArgumentException e) {
378             Log.e(TAG, "validateSsid failed: malformed string: " + ssid);
379             return false;
380         }
381         return true;
382     }
383 
validateBssid(MacAddress bssid)384     private static boolean validateBssid(MacAddress bssid) {
385         if (bssid == null) return true;
386         if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) {
387             Log.e(TAG, "validateBssid failed: invalid bssid");
388             return false;
389         }
390         return true;
391     }
392 
validateBssid(String bssid)393     private static boolean validateBssid(String bssid) {
394         if (bssid == null) return true;
395         if (bssid.isEmpty()) {
396             Log.e(TAG, "validateBssid failed: empty string");
397             return false;
398         }
399         MacAddress bssidMacAddress;
400         try {
401             bssidMacAddress = MacAddress.fromString(bssid);
402         } catch (IllegalArgumentException e) {
403             Log.e(TAG, "validateBssid failed: malformed string: " + bssid);
404             return false;
405         }
406         if (!validateBssid(bssidMacAddress)) {
407             return false;
408         }
409         return true;
410     }
411 
validatePassword(String password, boolean isAdd, boolean isSae)412     private static boolean validatePassword(String password, boolean isAdd, boolean isSae) {
413         if (isAdd) {
414             if (password == null) {
415                 Log.e(TAG, "validatePassword: null string");
416                 return false;
417             }
418         } else {
419             if (password == null) {
420                 // This is an update, so the psk can be null if that is not being changed.
421                 return true;
422             } else if (password.equals(PASSWORD_MASK)) {
423                 // This is an update, so the app might have returned back the masked password, let
424                 // it thru. WifiConfigManager will handle it.
425                 return true;
426             }
427         }
428         if (password.isEmpty()) {
429             Log.e(TAG, "validatePassword failed: empty string");
430             return false;
431         }
432         if (password.startsWith("\"")) {
433             // ASCII PSK string
434             byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
435             int targetMinLength;
436 
437             if (isSae) {
438                 targetMinLength = SAE_ASCII_MIN_LEN;
439             } else {
440                 targetMinLength = PSK_ASCII_MIN_LEN;
441             }
442             if (passwordBytes.length < targetMinLength) {
443                 Log.e(TAG, "validatePassword failed: ASCII string size too small: "
444                         + passwordBytes.length);
445                 return false;
446             }
447             if (passwordBytes.length > PSK_SAE_ASCII_MAX_LEN) {
448                 Log.e(TAG, "validatePassword failed: ASCII string size too large: "
449                         + passwordBytes.length);
450                 return false;
451             }
452         } else {
453             // HEX PSK string
454             if (password.length() != PSK_SAE_HEX_LEN) {
455                 Log.e(TAG, "validatePassword failed: hex string size mismatch: "
456                         + password.length());
457                 return false;
458             }
459         }
460         try {
461             NativeUtil.hexOrQuotedStringToBytes(password);
462         } catch (IllegalArgumentException e) {
463             Log.e(TAG, "validatePassword failed: malformed string: " + password);
464             return false;
465         }
466         return true;
467     }
468 
validateBitSet(BitSet bitSet, int validValuesLength)469     private static boolean validateBitSet(BitSet bitSet, int validValuesLength) {
470         if (bitSet == null) return false;
471         BitSet clonedBitset = (BitSet) bitSet.clone();
472         clonedBitset.clear(0, validValuesLength);
473         return clonedBitset.isEmpty();
474     }
475 
validateBitSets(WifiConfiguration config)476     private static boolean validateBitSets(WifiConfiguration config) {
477         // 1. Check |allowedKeyManagement|.
478         if (!validateBitSet(config.allowedKeyManagement,
479                 WifiConfiguration.KeyMgmt.strings.length)) {
480             Log.e(TAG, "validateBitsets failed: invalid allowedKeyManagement bitset "
481                     + config.allowedKeyManagement);
482             return false;
483         }
484         // 2. Check |allowedProtocols|.
485         if (!validateBitSet(config.allowedProtocols,
486                 WifiConfiguration.Protocol.strings.length)) {
487             Log.e(TAG, "validateBitsets failed: invalid allowedProtocols bitset "
488                     + config.allowedProtocols);
489             return false;
490         }
491         // 3. Check |allowedAuthAlgorithms|.
492         if (!validateBitSet(config.allowedAuthAlgorithms,
493                 WifiConfiguration.AuthAlgorithm.strings.length)) {
494             Log.e(TAG, "validateBitsets failed: invalid allowedAuthAlgorithms bitset "
495                     + config.allowedAuthAlgorithms);
496             return false;
497         }
498         // 4. Check |allowedGroupCiphers|.
499         if (!validateBitSet(config.allowedGroupCiphers,
500                 WifiConfiguration.GroupCipher.strings.length)) {
501             Log.e(TAG, "validateBitsets failed: invalid allowedGroupCiphers bitset "
502                     + config.allowedGroupCiphers);
503             return false;
504         }
505         // 5. Check |allowedPairwiseCiphers|.
506         if (!validateBitSet(config.allowedPairwiseCiphers,
507                 WifiConfiguration.PairwiseCipher.strings.length)) {
508             Log.e(TAG, "validateBitsets failed: invalid allowedPairwiseCiphers bitset "
509                     + config.allowedPairwiseCiphers);
510             return false;
511         }
512         return true;
513     }
514 
validateKeyMgmt(BitSet keyMgmnt)515     private static boolean validateKeyMgmt(BitSet keyMgmnt) {
516         if (keyMgmnt.cardinality() > 1) {
517             if (keyMgmnt.cardinality() > 3) {
518                 Log.e(TAG, "validateKeyMgmt failed: cardinality > 3");
519                 return false;
520             }
521             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
522                 Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP");
523                 return false;
524             }
525             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
526                     && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
527                 Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
528                 return false;
529             }
530             if (keyMgmnt.cardinality() == 3
531                     && !keyMgmnt.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
532                 Log.e(TAG, "validateKeyMgmt failed: not SUITE_B_192");
533                 return false;
534             }
535         }
536         return true;
537     }
538 
validateIpConfiguration(IpConfiguration ipConfig)539     private static boolean validateIpConfiguration(IpConfiguration ipConfig) {
540         if (ipConfig == null) {
541             Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration");
542             return false;
543         }
544         if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
545             StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration();
546             if (staticIpConfig == null) {
547                 Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration");
548                 return false;
549             }
550             if (staticIpConfig.ipAddress == null) {
551                 Log.e(TAG, "validateIpConfiguration failed: null static ip Address");
552                 return false;
553             }
554         }
555         return true;
556     }
557 
558     /**
559      * Enums to specify if the provided config is being validated for add or update.
560      */
561     public static final boolean VALIDATE_FOR_ADD = true;
562     public static final boolean VALIDATE_FOR_UPDATE = false;
563 
564     /**
565      * Validate the configuration received from an external application.
566      *
567      * This method checks for the following parameters:
568      * 1. {@link WifiConfiguration#SSID}
569      * 2. {@link WifiConfiguration#BSSID}
570      * 3. {@link WifiConfiguration#preSharedKey}
571      * 4. {@link WifiConfiguration#allowedKeyManagement}
572      * 5. {@link WifiConfiguration#allowedProtocols}
573      * 6. {@link WifiConfiguration#allowedAuthAlgorithms}
574      * 7. {@link WifiConfiguration#allowedGroupCiphers}
575      * 8. {@link WifiConfiguration#allowedPairwiseCiphers}
576      * 9. {@link WifiConfiguration#getIpConfiguration()}
577      *
578      * @param config {@link WifiConfiguration} received from an external application.
579      * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add,
580      *              {@link #VALIDATE_FOR_UPDATE} for a network config received for an update.
581      *              These 2 cases need to be handled differently because the config received for an
582      *              update could contain only the fields that are being changed.
583      * @return true if the parameters are valid, false otherwise.
584      */
validate(WifiConfiguration config, boolean isAdd)585     public static boolean validate(WifiConfiguration config, boolean isAdd) {
586         if (!validateSsid(config.SSID, isAdd)) {
587             return false;
588         }
589         if (!validateBssid(config.BSSID)) {
590             return false;
591         }
592         if (!validateBitSets(config)) {
593             return false;
594         }
595         if (!validateKeyMgmt(config.allowedKeyManagement)) {
596             return false;
597         }
598         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
599                 && !validatePassword(config.preSharedKey, isAdd, false)) {
600             return false;
601         }
602         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
603             // PMF mandatory for OWE networks
604             if (!config.requirePMF) {
605                 Log.e(TAG, "PMF must be enabled for OWE networks");
606                 return false;
607             }
608         }
609         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
610             // PMF mandatory for WPA3-Personal networks
611             if (!config.requirePMF) {
612                 Log.e(TAG, "PMF must be enabled for SAE networks");
613                 return false;
614             }
615             if (!validatePassword(config.preSharedKey, isAdd, true)) {
616                 return false;
617             }
618         }
619         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
620             // PMF mandatory for WPA3-Enterprise networks
621             if (!config.requirePMF) {
622                 Log.e(TAG, "PMF must be enabled for Suite-B 192-bit networks");
623                 return false;
624             }
625         }
626         if (!validateIpConfiguration(config.getIpConfiguration())) {
627             return false;
628         }
629         // TBD: Validate some enterprise params as well in the future here.
630         return true;
631     }
632 
validateBssidPattern( Pair<MacAddress, MacAddress> bssidPatternMatcher)633     private static boolean validateBssidPattern(
634             Pair<MacAddress, MacAddress> bssidPatternMatcher) {
635         if (bssidPatternMatcher == null) return true;
636         MacAddress baseAddress = bssidPatternMatcher.first;
637         MacAddress mask = bssidPatternMatcher.second;
638         if (baseAddress.getAddressType() != MacAddress.TYPE_UNICAST) {
639             Log.e(TAG, "validateBssidPatternMatcher failed : invalid base address: " + baseAddress);
640             return false;
641         }
642         if (mask.equals(MacAddress.ALL_ZEROS_ADDRESS)
643                 && !baseAddress.equals(MacAddress.ALL_ZEROS_ADDRESS)) {
644             Log.e(TAG, "validateBssidPatternMatcher failed : invalid mask/base: " + mask + "/"
645                     + baseAddress);
646             return false;
647         }
648         // TBD: Can we do any more checks?
649         return true;
650     }
651 
652     // TODO(b/113878056): Some of this is duplicated in {@link WifiNetworkConfigBuilder}.
653     // Merge them somehow?.
isValidNetworkSpecifier(WifiNetworkSpecifier specifier)654     private static boolean isValidNetworkSpecifier(WifiNetworkSpecifier specifier) {
655         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
656         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
657         if (ssidPatternMatcher == null || bssidPatternMatcher == null) {
658             return false;
659         }
660         if (ssidPatternMatcher.getPath() == null || bssidPatternMatcher.first == null
661                 || bssidPatternMatcher.second == null) {
662             return false;
663         }
664         return true;
665     }
666 
isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier)667     private static boolean isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier) {
668         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
669         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
670         if (ssidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX
671                 && ssidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) {
672             return true;
673         }
674         if (bssidPatternMatcher.equals(MATCH_NONE_BSSID_PATTERN)) {
675             return true;
676         }
677         return false;
678     }
679 
isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier)680     private static boolean isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier) {
681         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
682         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
683         if (ssidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH)
684                 && bssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) {
685             return true;
686         }
687         return false;
688     }
689 
690     /**
691      * Validate the configuration received from an external application inside
692      * {@link WifiNetworkSpecifier}.
693      *
694      * This method checks for the following parameters:
695      * 1. {@link WifiNetworkSpecifier#ssidPatternMatcher}
696      * 2. {@link WifiNetworkSpecifier#bssidPatternMatcher}
697      * 3. {@link WifiConfiguration#SSID}
698      * 4. {@link WifiConfiguration#BSSID}
699      * 5. {@link WifiConfiguration#preSharedKey}
700      * 6. {@link WifiConfiguration#allowedKeyManagement}
701      * 7. {@link WifiConfiguration#allowedProtocols}
702      * 8. {@link WifiConfiguration#allowedAuthAlgorithms}
703      * 9. {@link WifiConfiguration#allowedGroupCiphers}
704      * 10. {@link WifiConfiguration#allowedPairwiseCiphers}
705      * 11. {@link WifiConfiguration#getIpConfiguration()}
706      *
707      * @param specifier Instance of {@link WifiNetworkSpecifier}.
708      * @return true if the parameters are valid, false otherwise.
709      */
validateNetworkSpecifier(WifiNetworkSpecifier specifier)710     public static boolean validateNetworkSpecifier(WifiNetworkSpecifier specifier) {
711         if (!isValidNetworkSpecifier(specifier)) {
712             Log.e(TAG, "validateNetworkSpecifier failed : invalid network specifier");
713             return false;
714         }
715         if (isMatchNoneNetworkSpecifier(specifier)) {
716             Log.e(TAG, "validateNetworkSpecifier failed : match-none specifier");
717             return false;
718         }
719         if (isMatchAllNetworkSpecifier(specifier)) {
720             Log.e(TAG, "validateNetworkSpecifier failed : match-all specifier");
721             return false;
722         }
723         WifiConfiguration config = specifier.wifiConfiguration;
724         if (specifier.ssidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) {
725             // For literal SSID matches, the value should satisfy SSID requirements.
726             // WifiConfiguration.SSID needs quotes around ASCII SSID.
727             if (!validateSsid(addEnclosingQuotes(specifier.ssidPatternMatcher.getPath()), true)) {
728                 return false;
729             }
730         } else {
731             if (config.hiddenSSID) {
732                 Log.e(TAG, "validateNetworkSpecifier failed : ssid pattern not supported "
733                         + "for hidden networks");
734                 return false;
735             }
736         }
737         if (Objects.equals(specifier.bssidPatternMatcher.second, MacAddress.BROADCAST_ADDRESS)) {
738             // For literal BSSID matches, the value should satisfy MAC address requirements.
739             if (!validateBssid(specifier.bssidPatternMatcher.first)) {
740                 return false;
741             }
742         } else {
743             if (!validateBssidPattern(specifier.bssidPatternMatcher)) {
744                 return false;
745             }
746         }
747         if (!validateBitSets(config)) {
748             return false;
749         }
750         if (!validateKeyMgmt(config.allowedKeyManagement)) {
751             return false;
752         }
753         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
754                 && !validatePassword(config.preSharedKey, true, false)) {
755             return false;
756         }
757         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
758             // PMF mandatory for OWE networks
759             if (!config.requirePMF) {
760                 return false;
761             }
762         }
763         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
764             // PMF mandatory for WPA3-Personal networks
765             if (!config.requirePMF) {
766                 return false;
767             }
768             if (!validatePassword(config.preSharedKey, true, true)) {
769                 return false;
770             }
771         }
772         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
773             // PMF mandatory for WPA3-Enterprise networks
774             if (!config.requirePMF) {
775                 return false;
776             }
777         }
778         // TBD: Validate some enterprise params as well in the future here.
779         return true;
780     }
781 
782     /**
783      * Check if the provided two networks are the same.
784      * Note: This does not check if network selection BSSID's are the same.
785      *
786      * @param config  Configuration corresponding to a network.
787      * @param config1 Configuration corresponding to another network.
788      *
789      * @return true if |config| and |config1| are the same network.
790      *         false otherwise.
791      */
isSameNetwork(WifiConfiguration config, WifiConfiguration config1)792     public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
793         if (config == null && config1 == null) {
794             return true;
795         }
796         if (config == null || config1 == null) {
797             return false;
798         }
799         if (config.networkId != config1.networkId) {
800             return false;
801         }
802         if (!Objects.equals(config.SSID, config1.SSID)) {
803             return false;
804         }
805         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
806             return false;
807         }
808         return true;
809     }
810 
811     /**
812      * Create a PnoNetwork object from the provided WifiConfiguration.
813      *
814      * @param config      Configuration corresponding to the network.
815      * @return PnoNetwork object corresponding to the network.
816      */
createPnoNetwork( WifiConfiguration config)817     public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
818             WifiConfiguration config) {
819         WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
820                 new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
821         if (config.hiddenSSID) {
822             pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
823         }
824         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
825         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
826         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
827             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
828         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
829                 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
830             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
831         } else {
832             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
833         }
834         return pnoNetwork;
835     }
836 
837 
838     /**
839      * General WifiConfiguration list sorting algorithm:
840      * 1, Place the fully enabled networks first.
841      * 2. Next place all the temporarily disabled networks.
842      * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
843      * before WifiConfigManager uses this comparator today!).
844      *
845      * Among the networks with the same status, sort them in the order determined by the return of
846      * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
847      * implementation.
848      */
849     public abstract static class WifiConfigurationComparator implements
850             Comparator<WifiConfiguration> {
851         private static final int ENABLED_NETWORK_SCORE = 3;
852         private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
853         private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
854 
855         @Override
compare(WifiConfiguration a, WifiConfiguration b)856         public int compare(WifiConfiguration a, WifiConfiguration b) {
857             int configAScore = getNetworkStatusScore(a);
858             int configBScore = getNetworkStatusScore(b);
859             if (configAScore == configBScore) {
860                 return compareNetworksWithSameStatus(a, b);
861             } else {
862                 return Integer.compare(configBScore, configAScore);
863             }
864         }
865 
866         // This needs to be implemented by the connected/disconnected PNO list comparator.
compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b)867         abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
868 
869         /**
870          * Returns an integer representing a score for each configuration. The scores are assigned
871          * based on the status of the configuration. The scores are assigned according to the order:
872          * Fully enabled network > Temporarily disabled network > Permanently disabled network.
873          */
getNetworkStatusScore(WifiConfiguration config)874         private int getNetworkStatusScore(WifiConfiguration config) {
875             if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
876                 return ENABLED_NETWORK_SCORE;
877             } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
878                 return TEMPORARY_DISABLED_NETWORK_SCORE;
879             } else {
880                 return PERMANENTLY_DISABLED_NETWORK_SCORE;
881             }
882         }
883     }
884 }
885