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