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