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