• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.net.wifi.WifiManager.ALL_ZEROS_MAC_ADDRESS;
20 
21 import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
22 
23 import android.annotation.SuppressLint;
24 import android.net.IpConfiguration;
25 import android.net.MacAddress;
26 import android.net.StaticIpConfiguration;
27 import android.net.wifi.SecurityParams;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiEnterpriseConfig;
30 import android.net.wifi.WifiManager;
31 import android.net.wifi.WifiNetworkSpecifier;
32 import android.net.wifi.WifiScanner;
33 import android.os.PatternMatcher;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.util.Pair;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.modules.utils.build.SdkLevel;
40 import com.android.server.wifi.util.NativeUtil;
41 
42 import java.nio.charset.StandardCharsets;
43 import java.security.cert.X509Certificate;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.BitSet;
47 import java.util.Comparator;
48 import java.util.List;
49 import java.util.Objects;
50 
51 /**
52  * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations.
53  * Currently contains:
54  *   > Helper method to check if the WifiConfiguration object is visible to the provided users.
55  *   > Helper methods to identify the encryption of a WifiConfiguration object.
56  */
57 public class WifiConfigurationUtil {
58     private static final String TAG = "WifiConfigurationUtil";
59 
60     /**
61      * Constants used for validating external config objects.
62      */
63     private static final int ENCLOSING_QUOTES_LEN = 2;
64     private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN;
65     private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUOTES_LEN;
66     private static final int SSID_HEX_MIN_LEN = 2;
67     private static final int SSID_HEX_MAX_LEN = 64;
68     private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUOTES_LEN;
69     private static final int SAE_ASCII_MIN_LEN = 1 + ENCLOSING_QUOTES_LEN;
70     private static final int PSK_SAE_ASCII_MAX_LEN = 63 + ENCLOSING_QUOTES_LEN;
71     private static final int PSK_SAE_HEX_LEN = 64;
72     private static final int WEP104_KEY_BYTES_LEN = 13;
73     private static final int WEP40_KEY_BYTES_LEN = 5;
74 
75     @VisibleForTesting
76     public static final String PASSWORD_MASK = "*";
77     private static final String MATCH_EMPTY_SSID_PATTERN_PATH = "";
78     private static final Pair<MacAddress, MacAddress> MATCH_NONE_BSSID_PATTERN =
79             new Pair<>(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS);
80     private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN =
81             new Pair<>(ALL_ZEROS_MAC_ADDRESS, ALL_ZEROS_MAC_ADDRESS);
82 
83     /**
84      * Checks if the provided |wepKeys| array contains any non-null value;
85      */
hasAnyValidWepKey(String[] wepKeys)86     public static boolean hasAnyValidWepKey(String[] wepKeys) {
87         for (int i = 0; i < wepKeys.length; i++) {
88             if (wepKeys[i] != null) {
89                 return true;
90             }
91         }
92         return false;
93     }
94 
95     /**
96      * Helper method to check if the provided |config| corresponds to a PSK network or not.
97      */
isConfigForPskNetwork(WifiConfiguration config)98     public static boolean isConfigForPskNetwork(WifiConfiguration config) {
99         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK);
100     }
101 
102     /**
103      * Helper method to check if the provided |config| corresponds to a WAPI PSK network or not.
104      */
isConfigForWapiPskNetwork(WifiConfiguration config)105     public static boolean isConfigForWapiPskNetwork(WifiConfiguration config) {
106         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
107     }
108 
109     /**
110      * Helper method to check if the provided |config| corresponds to a WAPI CERT network or not.
111      */
isConfigForWapiCertNetwork(WifiConfiguration config)112     public static boolean isConfigForWapiCertNetwork(WifiConfiguration config) {
113         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
114     }
115 
116     /**
117      * Helper method to check if the provided |config| corresponds to an SAE network or not.
118      */
isConfigForSaeNetwork(WifiConfiguration config)119     public static boolean isConfigForSaeNetwork(WifiConfiguration config) {
120         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE);
121     }
122 
123     /**
124      * Helper method to check if the provided |config| corresponds to an OWE network or not.
125      */
isConfigForOweNetwork(WifiConfiguration config)126     public static boolean isConfigForOweNetwork(WifiConfiguration config) {
127         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE);
128     }
129 
130     /**
131      * Helper method to check if the provided |config| corresponds to a EAP network or not.
132      */
isConfigForEapNetwork(WifiConfiguration config)133     public static boolean isConfigForEapNetwork(WifiConfiguration config) {
134         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP);
135     }
136 
137     /**
138      * Helper method to check if the provided |config| corresponds to
139      * a WPA3 Enterprise network or not.
140      */
isConfigForWpa3EnterpriseNetwork(WifiConfiguration config)141     public static boolean isConfigForWpa3EnterpriseNetwork(WifiConfiguration config) {
142         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
143     }
144 
145     /**
146      * Helper method to check if the provided |config| corresponds to a EAP Suite-B network or not.
147      */
isConfigForWpa3Enterprise192BitNetwork(WifiConfiguration config)148     public static boolean isConfigForWpa3Enterprise192BitNetwork(WifiConfiguration config) {
149         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
150     }
151 
152     /**
153      * Helper method to check if the provided |config| corresponds to a WEP network or not.
154      */
isConfigForWepNetwork(WifiConfiguration config)155     public static boolean isConfigForWepNetwork(WifiConfiguration config) {
156         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WEP);
157     }
158 
159     /**
160      * Helper method to check if the provided |config| corresponds to a Passpoint network or not.
161      */
isConfigForPasspoint(WifiConfiguration config)162     public static boolean isConfigForPasspoint(WifiConfiguration config) {
163         return config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2)
164                 || config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3);
165     }
166 
167     /**
168      * Helper method to check if the provided |config| corresponds to an open or enhanced
169      * open network, or not.
170      */
isConfigForOpenNetwork(WifiConfiguration config)171     public static boolean isConfigForOpenNetwork(WifiConfiguration config) {
172         return (!(isConfigForWepNetwork(config) || isConfigForPskNetwork(config)
173                 || isConfigForWapiPskNetwork(config) || isConfigForWapiCertNetwork(config)
174                 || isConfigForEapNetwork(config) || isConfigForSaeNetwork(config)
175                 || isConfigForWpa3Enterprise192BitNetwork(config)
176                 || isConfigForPasspoint(config)));
177     }
178 
179     /**
180      * Compare existing and new WifiConfiguration objects after a network update and return if
181      * IP parameters have changed or not.
182      *
183      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
184      * @param newConfig      New WifiConfiguration object corresponding to the network.
185      * @return true if IP parameters have changed, false otherwise.
186      */
hasIpChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)187     public static boolean hasIpChanged(WifiConfiguration existingConfig,
188             WifiConfiguration newConfig) {
189         if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
190             return true;
191         }
192         if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
193             return !Objects.equals(existingConfig.getStaticIpConfiguration(),
194                     newConfig.getStaticIpConfiguration());
195         }
196         return false;
197     }
198 
199     /**
200      * Compare existing and new WifiConfiguration objects after a network update and return if
201      * proxy parameters have changed or not.
202      *
203      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
204      * @param newConfig      New WifiConfiguration object corresponding to the network.
205      * @return true if proxy parameters have changed, false if no existing config and proxy settings
206      * are NONE, false otherwise.
207      */
hasProxyChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)208     public static boolean hasProxyChanged(WifiConfiguration existingConfig,
209             WifiConfiguration newConfig) {
210         if (existingConfig == null) {
211             return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
212         }
213         if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
214             return true;
215         }
216         return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
217     }
218 
219     /**
220      * Compare existing and new WifiConfiguration objects after a network update and return if
221      * Repeater Enabled flag has changed or not. In case the there is no existing WifiConfiguration,
222      * checks if Repeater Enabled flag has changed from the default value of false.
223      *
224      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
225      * @param newConfig      New WifiConfiguration object corresponding to the network.
226      * @return true if RepeaterEnabled flag has changed, or if there is no existing config, and
227      * the flag is set to true, false otherwise.
228      */
hasRepeaterEnabledChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)229     public static boolean hasRepeaterEnabledChanged(WifiConfiguration existingConfig,
230             WifiConfiguration newConfig) {
231         if (existingConfig == null) {
232             return newConfig.isRepeaterEnabled();
233         }
234         return (newConfig.isRepeaterEnabled() != existingConfig.isRepeaterEnabled());
235     }
236 
237     /**
238      * Compare existing and new WifiConfiguration objects after a network update and return if
239      * MAC randomization setting has changed or not.
240      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
241      * @param newConfig      New WifiConfiguration object corresponding to the network.
242      * @return true if MAC randomization setting setting changed or the existing confiuration is
243      * null and the newConfig is setting macRandomizationSetting to the default value.
244      */
hasMacRandomizationSettingsChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)245     public static boolean hasMacRandomizationSettingsChanged(WifiConfiguration existingConfig,
246             WifiConfiguration newConfig) {
247         if (existingConfig == null) {
248             return newConfig.macRandomizationSetting != WifiConfiguration.RANDOMIZATION_AUTO;
249         }
250         return newConfig.macRandomizationSetting != existingConfig.macRandomizationSetting;
251     }
252 
253     /**
254      * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
255      * credential parameters have changed or not.
256      *
257      * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
258      *                                 network.
259      * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
260      * @return true if credentials have changed, false otherwise.
261      */
262     @VisibleForTesting
hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig, WifiEnterpriseConfig newEnterpriseConfig)263     public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
264             WifiEnterpriseConfig newEnterpriseConfig) {
265         if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
266             if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
267                 return true;
268             }
269             if (existingEnterpriseConfig.isAuthenticationSimBased()) {
270                 // The anonymous identity will be decorated with 3gpp realm in the service.
271                 if (!TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
272                         newEnterpriseConfig.getAnonymousIdentity())) {
273                     return true;
274                 }
275                 return false;
276             }
277             if (existingEnterpriseConfig.getPhase2Method()
278                     != newEnterpriseConfig.getPhase2Method()) {
279                 return true;
280             }
281             if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(),
282                                   newEnterpriseConfig.getIdentity())) {
283                 return true;
284             }
285             if (!TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(),
286                     newEnterpriseConfig.getAnonymousIdentity())) {
287                 return true;
288             }
289             if (!TextUtils.equals(existingEnterpriseConfig.getPassword(),
290                                     newEnterpriseConfig.getPassword())) {
291                 return true;
292             }
293             X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
294             X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
295             if (!Arrays.equals(existingCaCerts, newCaCerts)) {
296                 return true;
297             }
298             if (!Arrays.equals(newEnterpriseConfig.getCaCertificateAliases(),
299                     existingEnterpriseConfig.getCaCertificateAliases())) {
300                 return true;
301             }
302             if (!TextUtils.equals(newEnterpriseConfig.getClientCertificateAlias(),
303                     existingEnterpriseConfig.getClientCertificateAlias())) {
304                 return true;
305             }
306             if (!TextUtils.equals(newEnterpriseConfig.getAltSubjectMatch(),
307                     existingEnterpriseConfig.getAltSubjectMatch())) {
308                 return true;
309             }
310             if (!TextUtils.equals(newEnterpriseConfig.getWapiCertSuite(),
311                     existingEnterpriseConfig.getWapiCertSuite())) {
312                 return true;
313             }
314             if (newEnterpriseConfig.getOcsp() != existingEnterpriseConfig.getOcsp()) {
315                 return true;
316             }
317         } else {
318             // One of the configs may have an enterpriseConfig
319             if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
320                 return true;
321             }
322         }
323         return false;
324     }
325 
326     /**
327      * Compare existing and new WifiConfiguration objects after a network update and return if
328      * credential parameters have changed or not.
329      *
330      * @param existingConfig Existing WifiConfiguration object corresponding to the network.
331      * @param newConfig      New WifiConfiguration object corresponding to the network.
332      * @return true if credentials have changed, false otherwise.
333      */
hasCredentialChanged(WifiConfiguration existingConfig, WifiConfiguration newConfig)334     public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
335             WifiConfiguration newConfig) {
336         if (!Objects.equals(existingConfig.allowedKeyManagement,
337                 newConfig.allowedKeyManagement)) {
338             return true;
339         }
340         if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
341             return true;
342         }
343         if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
344                 newConfig.allowedAuthAlgorithms)) {
345             return true;
346         }
347         if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
348                 newConfig.allowedPairwiseCiphers)) {
349             return true;
350         }
351         if (!Objects.equals(existingConfig.allowedGroupCiphers,
352                 newConfig.allowedGroupCiphers)) {
353             return true;
354         }
355         if (!Objects.equals(existingConfig.allowedGroupManagementCiphers,
356                 newConfig.allowedGroupManagementCiphers)) {
357             return true;
358         }
359         if (!Objects.equals(existingConfig.allowedSuiteBCiphers,
360                 newConfig.allowedSuiteBCiphers)) {
361             return true;
362         }
363         if (!existingConfig.getSecurityParamsList().equals(newConfig.getSecurityParamsList())) {
364             return true;
365         }
366         if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
367             return true;
368         }
369         if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
370             return true;
371         }
372         if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
373             return true;
374         }
375         if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
376             return true;
377         }
378         if (existingConfig.requirePmf != newConfig.requirePmf) {
379             return true;
380         }
381         if (existingConfig.carrierId != newConfig.carrierId) {
382             return true;
383         }
384         if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
385                 newConfig.enterpriseConfig)) {
386             return true;
387         }
388         return false;
389     }
390 
validateSsid(String ssid, boolean isAdd)391     private static boolean validateSsid(String ssid, boolean isAdd) {
392         if (isAdd) {
393             if (ssid == null) {
394                 Log.e(TAG, "validateSsid : null string");
395                 return false;
396             }
397         } else {
398             if (ssid == null) {
399                 // This is an update, so the SSID can be null if that is not being changed.
400                 return true;
401             }
402         }
403         if (ssid.isEmpty()) {
404             Log.e(TAG, "validateSsid failed: empty string");
405             return false;
406         }
407         if (ssid.startsWith("\"")) {
408             // UTF-8 SSID string
409             byte[] ssidBytes = ssid.getBytes(StandardCharsets.UTF_8);
410             if (ssidBytes.length < SSID_UTF_8_MIN_LEN) {
411                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too small: "
412                         + ssidBytes.length);
413                 return false;
414             }
415             if (ssidBytes.length > SSID_UTF_8_MAX_LEN) {
416                 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too large: "
417                         + ssidBytes.length);
418                 return false;
419             }
420         } else {
421             // HEX SSID string
422             if (ssid.length() < SSID_HEX_MIN_LEN) {
423                 Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length());
424                 return false;
425             }
426             if (ssid.length() > SSID_HEX_MAX_LEN) {
427                 Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length());
428                 return false;
429             }
430         }
431         try {
432             NativeUtil.decodeSsid(ssid);
433         } catch (IllegalArgumentException e) {
434             Log.e(TAG, "validateSsid failed: malformed string: " + ssid);
435             return false;
436         }
437         return true;
438     }
439 
validateBssid(MacAddress bssid)440     private static boolean validateBssid(MacAddress bssid) {
441         if (bssid == null) return true;
442         if (bssid.getAddressType() != MacAddress.TYPE_UNICAST) {
443             Log.e(TAG, "validateBssid failed: invalid bssid");
444             return false;
445         }
446         return true;
447     }
448 
validateBssid(String bssid)449     private static boolean validateBssid(String bssid) {
450         if (bssid == null) return true;
451         if (bssid.isEmpty()) {
452             Log.e(TAG, "validateBssid failed: empty string");
453             return false;
454         }
455         // Allow reset of bssid with "any".
456         if (bssid.equals(ClientModeImpl.SUPPLICANT_BSSID_ANY)) return true;
457         MacAddress bssidMacAddress;
458         try {
459             bssidMacAddress = MacAddress.fromString(bssid);
460         } catch (IllegalArgumentException e) {
461             Log.e(TAG, "validateBssid failed: malformed string: " + bssid);
462             return false;
463         }
464         if (!validateBssid(bssidMacAddress)) {
465             return false;
466         }
467         return true;
468     }
469 
validatePassword(String password, boolean isAdd, boolean isSae)470     private static boolean validatePassword(String password, boolean isAdd, boolean isSae) {
471         if (isAdd) {
472             if (password == null) {
473                 Log.e(TAG, "validatePassword: null string");
474                 return false;
475             }
476         } else {
477             if (password == null) {
478                 // This is an update, so the psk can be null if that is not being changed.
479                 return true;
480             } else if (password.equals(PASSWORD_MASK)) {
481                 // This is an update, so the app might have returned back the masked password, let
482                 // it thru. WifiConfigManager will handle it.
483                 return true;
484             }
485         }
486         if (password.isEmpty()) {
487             Log.e(TAG, "validatePassword failed: empty string");
488             return false;
489         }
490         if (password.startsWith("\"")) {
491             // ASCII PSK string
492             byte[] passwordBytes = password.getBytes(StandardCharsets.US_ASCII);
493             int targetMinLength;
494 
495             if (isSae) {
496                 targetMinLength = SAE_ASCII_MIN_LEN;
497             } else {
498                 targetMinLength = PSK_ASCII_MIN_LEN;
499             }
500             if (passwordBytes.length < targetMinLength) {
501                 Log.e(TAG, "validatePassword failed: ASCII string size too small: "
502                         + passwordBytes.length);
503                 return false;
504             }
505             if (passwordBytes.length > PSK_SAE_ASCII_MAX_LEN) {
506                 Log.e(TAG, "validatePassword failed: ASCII string size too large: "
507                         + passwordBytes.length);
508                 return false;
509             }
510         } else {
511             // HEX PSK string
512             if (password.length() != PSK_SAE_HEX_LEN) {
513                 Log.e(TAG, "validatePassword failed: hex string size mismatch: "
514                         + password.length());
515                 return false;
516             }
517         }
518         try {
519             NativeUtil.hexOrQuotedStringToBytes(password);
520         } catch (IllegalArgumentException e) {
521             Log.e(TAG, "validatePassword failed: malformed string: " + password);
522             return false;
523         }
524         return true;
525     }
526 
validateWepKeys(String[] wepKeys, int wepTxKeyIndex, boolean isAdd)527     private static boolean validateWepKeys(String[] wepKeys, int wepTxKeyIndex, boolean isAdd) {
528         if (isAdd) {
529             if (wepKeys == null) {
530                 Log.e(TAG, "validateWepKeys: null string");
531                 return false;
532             }
533         } else {
534             if (wepKeys == null) {
535                 // This is an update, so the psk can be null if that is not being changed.
536                 return true;
537             } else {
538                 boolean allMaskedKeys = true;
539                 for (int i = 0; i < wepKeys.length; i++) {
540                     if (wepKeys[i] != null && !TextUtils.equals(wepKeys[i], PASSWORD_MASK)) {
541                         allMaskedKeys = false;
542                     }
543                 }
544                 if (allMaskedKeys) {
545                     // This is an update, so the app might have returned back the masked password,
546                     // let it thru. WifiConfigManager will handle it.
547                     return true;
548                 }
549             }
550         }
551         for (int i = 0; i < wepKeys.length; i++) {
552             if (wepKeys[i] != null) {
553                 try {
554                     ArrayList<Byte> wepKeyBytes =
555                             NativeUtil.hexOrQuotedStringToBytes(wepKeys[i]);
556                     if (wepKeyBytes.size() != WEP40_KEY_BYTES_LEN
557                             && wepKeyBytes.size() != WEP104_KEY_BYTES_LEN) {
558                         Log.e(TAG, "validateWepKeys: invalid wep key length "
559                                 + wepKeys[i].length() + " at index " + i);
560                         return false;
561                     }
562                 } catch (IllegalArgumentException e) {
563                     Log.e(TAG, "validateWepKeys: invalid wep key at index " + i, e);
564                     return false;
565                 }
566             }
567         }
568         if (wepTxKeyIndex >= wepKeys.length) {
569             Log.e(TAG, "validateWepKeys: invalid wep tx key index " + wepTxKeyIndex
570                     + " wepKeys len: " + wepKeys.length);
571             return false;
572         }
573         return true;
574     }
575 
validateBitSet(BitSet bitSet, int validValuesLength)576     private static boolean validateBitSet(BitSet bitSet, int validValuesLength) {
577         if (bitSet == null) return false;
578         BitSet clonedBitset = (BitSet) bitSet.clone();
579         clonedBitset.clear(0, validValuesLength);
580         return clonedBitset.isEmpty();
581     }
582 
validateBitSets(WifiConfiguration config)583     private static boolean validateBitSets(WifiConfiguration config) {
584         // 1. Check |allowedKeyManagement|.
585         if (!validateBitSet(config.allowedKeyManagement,
586                 WifiConfiguration.KeyMgmt.strings.length)) {
587             Log.e(TAG, "validateBitsets failed: invalid allowedKeyManagement bitset "
588                     + config.allowedKeyManagement);
589             return false;
590         }
591         // 2. Check |allowedProtocols|.
592         if (!validateBitSet(config.allowedProtocols,
593                 WifiConfiguration.Protocol.strings.length)) {
594             Log.e(TAG, "validateBitsets failed: invalid allowedProtocols bitset "
595                     + config.allowedProtocols);
596             return false;
597         }
598         // 3. Check |allowedAuthAlgorithms|.
599         if (!validateBitSet(config.allowedAuthAlgorithms,
600                 WifiConfiguration.AuthAlgorithm.strings.length)) {
601             Log.e(TAG, "validateBitsets failed: invalid allowedAuthAlgorithms bitset "
602                     + config.allowedAuthAlgorithms);
603             return false;
604         }
605         // 4. Check |allowedGroupCiphers|.
606         if (!validateBitSet(config.allowedGroupCiphers,
607                 WifiConfiguration.GroupCipher.strings.length)) {
608             Log.e(TAG, "validateBitsets failed: invalid allowedGroupCiphers bitset "
609                     + config.allowedGroupCiphers);
610             return false;
611         }
612         // 5. Check |allowedPairwiseCiphers|.
613         if (!validateBitSet(config.allowedPairwiseCiphers,
614                 WifiConfiguration.PairwiseCipher.strings.length)) {
615             Log.e(TAG, "validateBitsets failed: invalid allowedPairwiseCiphers bitset "
616                     + config.allowedPairwiseCiphers);
617             return false;
618         }
619         return true;
620     }
621 
validateKeyMgmt(BitSet keyMgmnt)622     private static boolean validateKeyMgmt(BitSet keyMgmnt) {
623         if (keyMgmnt.cardinality() > 1) {
624             if (keyMgmnt.cardinality() > 3) {
625                 Log.e(TAG, "validateKeyMgmt failed: cardinality > 3");
626                 return false;
627             }
628             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
629                 Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP");
630                 return false;
631             }
632             if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
633                     && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
634                 Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X");
635                 return false;
636             }
637             // SUITE-B keymgmt must be WPA_EAP + IEEE8021X + SUITE_B_192.
638             if (keyMgmnt.cardinality() == 3
639                     && !(keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)
640                             && keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X)
641                             && keyMgmnt.get(WifiConfiguration.KeyMgmt.SUITE_B_192))) {
642                 Log.e(TAG, "validateKeyMgmt failed: not SUITE_B_192");
643                 return false;
644             }
645         }
646         return true;
647     }
648 
validateIpConfiguration(IpConfiguration ipConfig)649     private static boolean validateIpConfiguration(IpConfiguration ipConfig) {
650         if (ipConfig == null) {
651             Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration");
652             return false;
653         }
654         if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
655             StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration();
656             if (staticIpConfig == null) {
657                 Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration");
658                 return false;
659             }
660             if (staticIpConfig.getIpAddress() == null) {
661                 Log.e(TAG, "validateIpConfiguration failed: null static ip Address");
662                 return false;
663             }
664         }
665         return true;
666     }
667 
668     /**
669      * Enums to specify if the provided config is being validated for add or update.
670      */
671     public static final boolean VALIDATE_FOR_ADD = true;
672     public static final boolean VALIDATE_FOR_UPDATE = false;
673 
674     /**
675      * Validate the configuration received from an external application.
676      *
677      * This method checks for the following parameters:
678      * 1. {@link WifiConfiguration#SSID}
679      * 2. {@link WifiConfiguration#BSSID}
680      * 3. {@link WifiConfiguration#preSharedKey}
681      * 4. {@link WifiConfiguration#allowedKeyManagement}
682      * 5. {@link WifiConfiguration#allowedProtocols}
683      * 6. {@link WifiConfiguration#allowedAuthAlgorithms}
684      * 7. {@link WifiConfiguration#allowedGroupCiphers}
685      * 8. {@link WifiConfiguration#allowedPairwiseCiphers}
686      * 9. {@link WifiConfiguration#getIpConfiguration()}
687      *
688      * @param config {@link WifiConfiguration} received from an external application.
689      * @param supportedFeatureSet bitmask for supported features using {@code WIFI_FEATURE_}
690      * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add,
691      *              {@link #VALIDATE_FOR_UPDATE} for a network config received for an update.
692      *              These 2 cases need to be handled differently because the config received for an
693      *              update could contain only the fields that are being changed.
694      * @return true if the parameters are valid, false otherwise.
695      */
validate(WifiConfiguration config, long supportedFeatureSet, boolean isAdd)696     public static boolean validate(WifiConfiguration config, long supportedFeatureSet,
697             boolean isAdd) {
698         if (!validateSsid(config.SSID, isAdd)) {
699             return false;
700         }
701         if (!validateBssid(config.BSSID)) {
702             return false;
703         }
704         if (!validateBitSets(config)) {
705             return false;
706         }
707         if (!validateKeyMgmt(config.allowedKeyManagement)) {
708             return false;
709         }
710         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WEP)
711                 && config.wepKeys != null
712                 && !validateWepKeys(config.wepKeys, config.wepTxKeyIndex, isAdd)) {
713             return false;
714         }
715         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
716                 && !validatePassword(config.preSharedKey, isAdd, false)) {
717             return false;
718         }
719         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
720                 && !validatePassword(config.preSharedKey, isAdd, true)) {
721             return false;
722         }
723         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_WAPI_PSK)
724                 && !validatePassword(config.preSharedKey, isAdd, false)) {
725             return false;
726         }
727         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_DPP)
728                 && (supportedFeatureSet & WifiManager.WIFI_FEATURE_DPP_AKM) == 0) {
729             Log.e(TAG, "DPP AKM is not supported");
730             return false;
731         }
732         if (!validateEnterpriseConfig(config, isAdd)) {
733             return false;
734         }
735 
736         // b/153435438: Added to deal with badly formed WifiConfiguration from apps.
737         if (config.preSharedKey != null && !config.needsPreSharedKey()) {
738             Log.e(TAG, "preSharedKey set with an invalid KeyMgmt, resetting KeyMgmt to WPA_PSK");
739             config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
740         }
741         if (!validateIpConfiguration(config.getIpConfiguration())) {
742             return false;
743         }
744         // TBD: Validate some enterprise params as well in the future here.
745         return true;
746     }
747 
validateBssidPattern( Pair<MacAddress, MacAddress> bssidPatternMatcher)748     private static boolean validateBssidPattern(
749             Pair<MacAddress, MacAddress> bssidPatternMatcher) {
750         if (bssidPatternMatcher == null) return true;
751         MacAddress baseAddress = bssidPatternMatcher.first;
752         MacAddress mask = bssidPatternMatcher.second;
753         if (baseAddress.getAddressType() != MacAddress.TYPE_UNICAST) {
754             Log.e(TAG, "validateBssidPatternMatcher failed : invalid base address: " + baseAddress);
755             return false;
756         }
757         if (mask.equals(ALL_ZEROS_MAC_ADDRESS)
758                 && !baseAddress.equals(ALL_ZEROS_MAC_ADDRESS)) {
759             Log.e(TAG, "validateBssidPatternMatcher failed : invalid mask/base: " + mask + "/"
760                     + baseAddress);
761             return false;
762         }
763         return true;
764     }
765 
766     // TODO(b/113878056): Some of this is duplicated in {@link WifiNetworkConfigBuilder}.
767     // Merge them somehow?.
isValidNetworkSpecifier(WifiNetworkSpecifier specifier)768     private static boolean isValidNetworkSpecifier(WifiNetworkSpecifier specifier) {
769         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
770         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
771         if (ssidPatternMatcher == null || bssidPatternMatcher == null) {
772             return false;
773         }
774         if (ssidPatternMatcher.getPath() == null || bssidPatternMatcher.first == null
775                 || bssidPatternMatcher.second == null) {
776             return false;
777         }
778         return true;
779     }
780 
isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier)781     private static boolean isMatchNoneNetworkSpecifier(WifiNetworkSpecifier specifier) {
782         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
783         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
784         if (ssidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX
785                 && ssidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) {
786             return true;
787         }
788         if (bssidPatternMatcher.equals(MATCH_NONE_BSSID_PATTERN)) {
789             return true;
790         }
791         return false;
792     }
793 
794     /**
795      * Check if the network specifier matches all networks.
796      * @param specifier The network specifier
797      * @return true if it matches all networks.
798      */
isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier)799     public static boolean isMatchAllNetworkSpecifier(WifiNetworkSpecifier specifier) {
800         PatternMatcher ssidPatternMatcher = specifier.ssidPatternMatcher;
801         Pair<MacAddress, MacAddress> bssidPatternMatcher = specifier.bssidPatternMatcher;
802         if (ssidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH)
803                 && bssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) {
804             return true;
805         }
806         return false;
807     }
808 
809     // TODO: b/177434707 calls inside same module are safe
810     @SuppressLint("NewApi")
getBand(WifiNetworkSpecifier s)811     private static int getBand(WifiNetworkSpecifier s) {
812         return s.getBand();
813     }
814 
815     /**
816      * Validate the configuration received from an external application inside
817      * {@link WifiNetworkSpecifier}.
818      *
819      * This method checks for the following parameters:
820      * 1. {@link WifiNetworkSpecifier#ssidPatternMatcher}
821      * 2. {@link WifiNetworkSpecifier#bssidPatternMatcher}
822      * 3. {@link WifiNetworkSpecifier#getBand()}
823      * 4. {@link WifiConfiguration#SSID}
824      * 5. {@link WifiConfiguration#BSSID}
825      * 6. {@link WifiConfiguration#preSharedKey}
826      * 7. {@link WifiConfiguration#allowedKeyManagement}
827      * 8. {@link WifiConfiguration#allowedProtocols}
828      * 9. {@link WifiConfiguration#allowedAuthAlgorithms}
829      * 10. {@link WifiConfiguration#allowedGroupCiphers}
830      * 11. {@link WifiConfiguration#allowedPairwiseCiphers}
831      * 12. {@link WifiConfiguration#getIpConfiguration()}
832      *
833      * @param specifier Instance of {@link WifiNetworkSpecifier}.
834      * @return true if the parameters are valid, false otherwise.
835      */
validateNetworkSpecifier(WifiNetworkSpecifier specifier)836     public static boolean validateNetworkSpecifier(WifiNetworkSpecifier specifier) {
837         if (!isValidNetworkSpecifier(specifier)) {
838             Log.e(TAG, "validateNetworkSpecifier failed : invalid network specifier");
839             return false;
840         }
841         if (isMatchNoneNetworkSpecifier(specifier)) {
842             Log.e(TAG, "validateNetworkSpecifier failed : match-none specifier");
843             return false;
844         }
845         if (isMatchAllNetworkSpecifier(specifier)) {
846             Log.e(TAG, "validateNetworkSpecifier failed : match-all specifier");
847             return false;
848         }
849         if (!WifiNetworkSpecifier.validateBand(getBand(specifier))) {
850             return false;
851         }
852         WifiConfiguration config = specifier.wifiConfiguration;
853         if (specifier.ssidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) {
854             // For literal SSID matches, the value should satisfy SSID requirements.
855             // WifiConfiguration.SSID needs quotes around ASCII SSID.
856             if (!validateSsid(addEnclosingQuotes(specifier.ssidPatternMatcher.getPath()), true)) {
857                 return false;
858             }
859         } else {
860             if (config.hiddenSSID) {
861                 Log.e(TAG, "validateNetworkSpecifier failed : ssid pattern not supported "
862                         + "for hidden networks");
863                 return false;
864             }
865         }
866         if (Objects.equals(specifier.bssidPatternMatcher.second, MacAddress.BROADCAST_ADDRESS)) {
867             // For literal BSSID matches, the value should satisfy MAC address requirements.
868             if (!validateBssid(specifier.bssidPatternMatcher.first)) {
869                 return false;
870             }
871         } else {
872             if (!validateBssidPattern(specifier.bssidPatternMatcher)) {
873                 return false;
874             }
875         }
876         if (!validateBitSets(config)) {
877             return false;
878         }
879         if (!validateKeyMgmt(config.allowedKeyManagement)) {
880             return false;
881         }
882         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)
883                 && !validatePassword(config.preSharedKey, true, false)) {
884             return false;
885         }
886         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)
887                 && !validatePassword(config.preSharedKey, true, true)) {
888             return false;
889         }
890         // TBD: Validate some enterprise params as well in the future here.
891         return true;
892     }
893 
894     /**
895      * Check if the provided two networks are the same.
896      * Note: This does not check if network selection BSSID's are the same.
897      *
898      * @param config  Configuration corresponding to a network.
899      * @param config1 Configuration corresponding to another network.
900      *
901      * @return true if |config| and |config1| are the same network.
902      *         false otherwise.
903      */
isSameNetwork(WifiConfiguration config, WifiConfiguration config1)904     public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
905         if (config == null && config1 == null) {
906             return true;
907         }
908         if (config == null || config1 == null) {
909             return false;
910         }
911         if (config.networkId != config1.networkId) {
912             return false;
913         }
914         if (!Objects.equals(config.SSID, config1.SSID)) {
915             return false;
916         }
917         if (!Objects.equals(config.getNetworkSelectionStatus().getCandidateSecurityParams(),
918                 config1.getNetworkSelectionStatus().getCandidateSecurityParams())) {
919             return false;
920         }
921         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
922             return false;
923         }
924         return true;
925     }
926 
927     /**
928      * Create a PnoNetwork object from the provided WifiConfiguration.
929      *
930      * @param config      Configuration corresponding to the network.
931      * @return PnoNetwork object corresponding to the network.
932      */
createPnoNetwork( WifiConfiguration config)933     public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
934             WifiConfiguration config) {
935         WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
936                 new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
937         if (config.hiddenSSID) {
938             pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
939         }
940         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
941         pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
942         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) {
943             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
944         } else if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)) {
945             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
946         } else {
947             pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
948         }
949         return pnoNetwork;
950     }
951 
addOpenUpgradableSecurityTypeIfNecessary(WifiConfiguration config)952     private static void addOpenUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
953         if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OPEN)) return;
954         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) return;
955 
956         Log.d(TAG, "Add upgradable OWE configuration.");
957         SecurityParams oweParams = SecurityParams.createSecurityParamsBySecurityType(
958                 WifiConfiguration.SECURITY_TYPE_OWE);
959         oweParams.setIsAddedByAutoUpgrade(true);
960         config.addSecurityParams(oweParams);
961     }
962 
addPskUpgradableSecurityTypeIfNecessary(WifiConfiguration config)963     private static void addPskUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
964         if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK)) return;
965         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) return;
966 
967         Log.d(TAG, "Add upgradable SAE configuration.");
968         SecurityParams saeParams = SecurityParams.createSecurityParamsBySecurityType(
969                 WifiConfiguration.SECURITY_TYPE_SAE);
970         saeParams.setIsAddedByAutoUpgrade(true);
971         config.addSecurityParams(saeParams);
972     }
973 
addEapUpgradableSecurityTypeIfNecessary(WifiConfiguration config)974     private static void addEapUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
975         if (!config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)) return;
976         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) return;
977 
978         Log.d(TAG, "Add upgradable Enterprise configuration.");
979         SecurityParams wpa3EnterpriseParams = SecurityParams.createSecurityParamsBySecurityType(
980                 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
981         wpa3EnterpriseParams.setIsAddedByAutoUpgrade(true);
982         config.addSecurityParams(wpa3EnterpriseParams);
983     }
984 
985     /**
986      * Add upgradable securit type to the given wifi configuration.
987      *
988      * @param config the wifi configuration to be checked.
989      */
addUpgradableSecurityTypeIfNecessary(WifiConfiguration config)990     public static boolean addUpgradableSecurityTypeIfNecessary(WifiConfiguration config) {
991         try {
992             addOpenUpgradableSecurityTypeIfNecessary(config);
993             addPskUpgradableSecurityTypeIfNecessary(config);
994             addEapUpgradableSecurityTypeIfNecessary(config);
995         } catch (IllegalArgumentException e) {
996             Log.e(TAG, "Failed to add upgradable security type");
997             return false;
998         }
999         return true;
1000     }
1001 
1002     /**
1003      * For a upgradable type which is added by the auto-upgrade mechenism, it is only
1004      * matched when corresponding auto-upgrade features are enabled.
1005      */
shouldOmitAutoUpgradeParams(SecurityParams params)1006     private static boolean shouldOmitAutoUpgradeParams(SecurityParams params) {
1007         if (!params.isAddedByAutoUpgrade()) return false;
1008 
1009         WifiGlobals wifiGlobals = WifiInjector.getInstance().getWifiGlobals();
1010 
1011         if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE)) {
1012             return !wifiGlobals.isWpa3SaeUpgradeEnabled();
1013         }
1014         if (params.isSecurityType(WifiConfiguration.SECURITY_TYPE_OWE)) {
1015             return !wifiGlobals.isOweUpgradeEnabled();
1016         }
1017         return false;
1018     }
1019 
isSecurityParamsSupported(SecurityParams params)1020     private static boolean isSecurityParamsSupported(SecurityParams params) {
1021         final long wifiFeatures = WifiInjector.getInstance()
1022                 .getActiveModeWarden().getPrimaryClientModeManager()
1023                 .getSupportedFeatures();
1024         switch (params.getSecurityType()) {
1025             case WifiConfiguration.SECURITY_TYPE_SAE:
1026                 return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_WPA3_SAE);
1027             case WifiConfiguration.SECURITY_TYPE_OWE:
1028                 return 0 != (wifiFeatures & WifiManager.WIFI_FEATURE_OWE);
1029         }
1030         return true;
1031     }
1032 
1033     /**
1034      * Check the security params is valid or not.
1035      * @param params the requesting security params.
1036      * @return true if it's valid; otherwise false.
1037      */
isSecurityParamsValid(SecurityParams params)1038     public static boolean isSecurityParamsValid(SecurityParams params) {
1039         if (!params.isEnabled()) return false;
1040         if (!isSecurityParamsSupported(params)) return false;
1041         return true;
1042     }
1043 
1044     /**
1045      * General WifiConfiguration list sorting algorithm:
1046      * 1, Place the fully enabled networks first.
1047      * 2. Next place all the temporarily disabled networks.
1048      * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
1049      * before WifiConfigManager uses this comparator today!).
1050      *
1051      * Among the networks with the same status, sort them in the order determined by the return of
1052      * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
1053      * implementation.
1054      */
1055     public abstract static class WifiConfigurationComparator implements
1056             Comparator<WifiConfiguration> {
1057         private static final int ENABLED_NETWORK_SCORE = 3;
1058         private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
1059         private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
1060 
1061         @Override
compare(WifiConfiguration a, WifiConfiguration b)1062         public int compare(WifiConfiguration a, WifiConfiguration b) {
1063             int configAScore = getNetworkStatusScore(a);
1064             int configBScore = getNetworkStatusScore(b);
1065             if (configAScore == configBScore) {
1066                 return compareNetworksWithSameStatus(a, b);
1067             } else {
1068                 return Integer.compare(configBScore, configAScore);
1069             }
1070         }
1071 
1072         // This needs to be implemented by the connected/disconnected PNO list comparator.
compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b)1073         abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
1074 
1075         /**
1076          * Returns an integer representing a score for each configuration. The scores are assigned
1077          * based on the status of the configuration. The scores are assigned according to the order:
1078          * Fully enabled network > Temporarily disabled network > Permanently disabled network.
1079          */
getNetworkStatusScore(WifiConfiguration config)1080         private int getNetworkStatusScore(WifiConfiguration config) {
1081             if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
1082                 return ENABLED_NETWORK_SCORE;
1083             } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
1084                 return TEMPORARY_DISABLED_NETWORK_SCORE;
1085             } else {
1086                 return PERMANENTLY_DISABLED_NETWORK_SCORE;
1087             }
1088         }
1089     }
1090 
1091     /**
1092      * Convert multi-type configurations to a list of configurations with a single security type,
1093      * where a configuration with multiple security configurations will be converted to multiple
1094      * Wi-Fi configurations with a single security type.
1095      * For R or older releases, Settings/WifiTrackerLib does not handle multiple configurations
1096      * with the same SSID and will result in duplicate saved networks. As a result, just return
1097      * the merged configs directly.
1098      *
1099      * @param configs the list of multi-type configurations.
1100      * @return a list of Wi-Fi configurations with a single security type,
1101      *         that may contain multiple configurations with the same network ID.
1102      */
convertMultiTypeConfigsToLegacyConfigs( List<WifiConfiguration> configs)1103     public static List<WifiConfiguration> convertMultiTypeConfigsToLegacyConfigs(
1104             List<WifiConfiguration> configs) {
1105         if (!SdkLevel.isAtLeastS()) {
1106             return configs;
1107         }
1108         List<WifiConfiguration> legacyConfigs = new ArrayList<>();
1109         for (WifiConfiguration config : configs) {
1110             for (SecurityParams params: config.getSecurityParamsList()) {
1111                 if (!params.isEnabled()) continue;
1112                 if (shouldOmitAutoUpgradeParams(params)) continue;
1113                 WifiConfiguration legacyConfig = new WifiConfiguration(config);
1114                 legacyConfig.setSecurityParams(params);
1115                 legacyConfigs.add(legacyConfig);
1116             }
1117         }
1118         return legacyConfigs;
1119     }
1120 
validateEnterpriseConfig(WifiConfiguration config, boolean isAdd)1121     private static boolean validateEnterpriseConfig(WifiConfiguration config, boolean isAdd) {
1122         if ((config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP)
1123                 || config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE))
1124                 && !config.isEnterprise()) {
1125             return false;
1126         }
1127         if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)
1128                 && (!config.isEnterprise()
1129                 || config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS)) {
1130             return false;
1131         }
1132         if (config.isEnterprise()) {
1133             if (config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP
1134                     || config.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS) {
1135 
1136                 int phase2Method = config.enterpriseConfig.getPhase2Method();
1137                 if (phase2Method == WifiEnterpriseConfig.Phase2.MSCHAP
1138                         || phase2Method == WifiEnterpriseConfig.Phase2.MSCHAPV2
1139                         || phase2Method == WifiEnterpriseConfig.Phase2.PAP
1140                         || phase2Method == WifiEnterpriseConfig.Phase2.GTC) {
1141                     // Check the password on add only. When updating, the password may not be
1142                     // available and it appears as "(Unchanged)" in Settings
1143                     if ((isAdd && TextUtils.isEmpty(config.enterpriseConfig.getPassword()))
1144                             || TextUtils.isEmpty(config.enterpriseConfig.getIdentity())) {
1145                         Log.e(TAG, "Enterprise network without an identity or a password set");
1146                         return false;
1147                     }
1148                 }
1149             }
1150         }
1151         return true;
1152     }
1153 }
1154