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 android.content.pm.UserInfo; 20 import android.net.IpConfiguration; 21 import android.net.StaticIpConfiguration; 22 import android.net.wifi.WifiConfiguration; 23 import android.net.wifi.WifiEnterpriseConfig; 24 import android.net.wifi.WifiScanner; 25 import android.os.UserHandle; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.wifi.util.NativeUtil; 31 32 import java.nio.charset.StandardCharsets; 33 import java.security.cert.X509Certificate; 34 import java.util.Arrays; 35 import java.util.BitSet; 36 import java.util.Comparator; 37 import java.util.List; 38 import java.util.Objects; 39 40 /** 41 * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations. 42 * Currently contains: 43 * > Helper method to check if the WifiConfiguration object is visible to the provided users. 44 * > Helper methods to identify the encryption of a WifiConfiguration object. 45 */ 46 public class WifiConfigurationUtil { 47 private static final String TAG = "WifiConfigurationUtil"; 48 49 /** 50 * Constants used for validating external config objects. 51 */ 52 private static final int ENCLOSING_QUTOES_LEN = 2; 53 private static final int SSID_UTF_8_MIN_LEN = 1 + ENCLOSING_QUTOES_LEN; 54 private static final int SSID_UTF_8_MAX_LEN = 32 + ENCLOSING_QUTOES_LEN; 55 private static final int SSID_HEX_MIN_LEN = 2; 56 private static final int SSID_HEX_MAX_LEN = 64; 57 private static final int PSK_ASCII_MIN_LEN = 8 + ENCLOSING_QUTOES_LEN; 58 private static final int PSK_ASCII_MAX_LEN = 63 + ENCLOSING_QUTOES_LEN; 59 private static final int PSK_HEX_LEN = 64; 60 @VisibleForTesting 61 public static final String PASSWORD_MASK = "*"; 62 63 /** 64 * Check whether a network configuration is visible to a user or any of its managed profiles. 65 * 66 * @param config the network configuration whose visibility should be checked 67 * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained 68 * via {@link android.os.UserManager#getProfiles}) 69 * @return whether the network configuration is visible to the user or any of its managed 70 * profiles 71 */ 72 public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) { 73 return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles)); 74 } 75 76 /** 77 * Check whether a uid belong to a user or any of its managed profiles. 78 * 79 * @param uid uid of the app. 80 * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained 81 * via {@link android.os.UserManager#getProfiles}) 82 * @return whether the uid belongs to the user or any of its managed profiles. 83 */ 84 public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) { 85 final int userId = UserHandle.getUserId(uid); 86 for (UserInfo profile : profiles) { 87 if (profile.id == userId) { 88 return true; 89 } 90 } 91 return false; 92 } 93 94 /** 95 * Checks if the provided |wepKeys| array contains any non-null value; 96 */ 97 public static boolean hasAnyValidWepKey(String[] wepKeys) { 98 for (int i = 0; i < wepKeys.length; i++) { 99 if (wepKeys[i] != null) { 100 return true; 101 } 102 } 103 return false; 104 } 105 106 /** 107 * Helper method to check if the provided |config| corresponds to a PSK network or not. 108 */ 109 public static boolean isConfigForPskNetwork(WifiConfiguration config) { 110 return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK); 111 } 112 113 /** 114 * Helper method to check if the provided |config| corresponds to a EAP network or not. 115 */ 116 public static boolean isConfigForEapNetwork(WifiConfiguration config) { 117 return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) 118 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); 119 } 120 121 /** 122 * Helper method to check if the provided |config| corresponds to a WEP network or not. 123 */ 124 public static boolean isConfigForWepNetwork(WifiConfiguration config) { 125 return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE) 126 && hasAnyValidWepKey(config.wepKeys)); 127 } 128 129 /** 130 * Helper method to check if the provided |config| corresponds to an open network or not. 131 */ 132 public static boolean isConfigForOpenNetwork(WifiConfiguration config) { 133 return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config) 134 || isConfigForEapNetwork(config)); 135 } 136 137 /** 138 * Compare existing and new WifiConfiguration objects after a network update and return if 139 * IP parameters have changed or not. 140 * 141 * @param existingConfig Existing WifiConfiguration object corresponding to the network. 142 * @param newConfig New WifiConfiguration object corresponding to the network. 143 * @return true if IP parameters have changed, false otherwise. 144 */ 145 public static boolean hasIpChanged(WifiConfiguration existingConfig, 146 WifiConfiguration newConfig) { 147 if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) { 148 return true; 149 } 150 if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) { 151 return !Objects.equals(existingConfig.getStaticIpConfiguration(), 152 newConfig.getStaticIpConfiguration()); 153 } 154 return false; 155 } 156 157 /** 158 * Compare existing and new WifiConfiguration objects after a network update and return if 159 * proxy parameters have changed or not. 160 * 161 * @param existingConfig Existing WifiConfiguration object corresponding to the network. 162 * @param newConfig New WifiConfiguration object corresponding to the network. 163 * @return true if proxy parameters have changed, false if no existing config and proxy settings 164 * are NONE, false otherwise. 165 */ 166 public static boolean hasProxyChanged(WifiConfiguration existingConfig, 167 WifiConfiguration newConfig) { 168 if (existingConfig == null) { 169 return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE; 170 } 171 if (newConfig.getProxySettings() != existingConfig.getProxySettings()) { 172 return true; 173 } 174 return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy()); 175 } 176 177 /** 178 * Compare existing and new WifiEnterpriseConfig objects after a network update and return if 179 * credential parameters have changed or not. 180 * 181 * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the 182 * network. 183 * @param newEnterpriseConfig New WifiConfiguration object corresponding to the network. 184 * @return true if credentials have changed, false otherwise. 185 */ 186 @VisibleForTesting 187 public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig, 188 WifiEnterpriseConfig newEnterpriseConfig) { 189 if (existingEnterpriseConfig != null && newEnterpriseConfig != null) { 190 if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) { 191 return true; 192 } 193 if (existingEnterpriseConfig.getPhase2Method() 194 != newEnterpriseConfig.getPhase2Method()) { 195 return true; 196 } 197 if (!TextUtils.equals(existingEnterpriseConfig.getIdentity(), 198 newEnterpriseConfig.getIdentity()) 199 || !TextUtils.equals(existingEnterpriseConfig.getAnonymousIdentity(), 200 newEnterpriseConfig.getAnonymousIdentity())) { 201 return true; 202 } 203 if (!TextUtils.equals(existingEnterpriseConfig.getPassword(), 204 newEnterpriseConfig.getPassword())) { 205 return true; 206 } 207 X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates(); 208 X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates(); 209 if (!Arrays.equals(existingCaCerts, newCaCerts)) { 210 return true; 211 } 212 } else { 213 // One of the configs may have an enterpriseConfig 214 if (existingEnterpriseConfig != null || newEnterpriseConfig != null) { 215 return true; 216 } 217 } 218 return false; 219 } 220 221 /** 222 * Compare existing and new WifiConfiguration objects after a network update and return if 223 * credential parameters have changed or not. 224 * 225 * @param existingConfig Existing WifiConfiguration object corresponding to the network. 226 * @param newConfig New WifiConfiguration object corresponding to the network. 227 * @return true if credentials have changed, false otherwise. 228 */ 229 public static boolean hasCredentialChanged(WifiConfiguration existingConfig, 230 WifiConfiguration newConfig) { 231 if (!Objects.equals(existingConfig.allowedKeyManagement, 232 newConfig.allowedKeyManagement)) { 233 return true; 234 } 235 if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) { 236 return true; 237 } 238 if (!Objects.equals(existingConfig.allowedAuthAlgorithms, 239 newConfig.allowedAuthAlgorithms)) { 240 return true; 241 } 242 if (!Objects.equals(existingConfig.allowedPairwiseCiphers, 243 newConfig.allowedPairwiseCiphers)) { 244 return true; 245 } 246 if (!Objects.equals(existingConfig.allowedGroupCiphers, 247 newConfig.allowedGroupCiphers)) { 248 return true; 249 } 250 if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) { 251 return true; 252 } 253 if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) { 254 return true; 255 } 256 if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) { 257 return true; 258 } 259 if (existingConfig.hiddenSSID != newConfig.hiddenSSID) { 260 return true; 261 } 262 if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig, 263 newConfig.enterpriseConfig)) { 264 return true; 265 } 266 return false; 267 } 268 269 private static boolean validateSsid(String ssid, boolean isAdd) { 270 if (isAdd) { 271 if (ssid == null) { 272 Log.e(TAG, "validateSsid : null string"); 273 return false; 274 } 275 } else { 276 if (ssid == null) { 277 // This is an update, so the SSID can be null if that is not being changed. 278 return true; 279 } 280 } 281 if (ssid.isEmpty()) { 282 Log.e(TAG, "validateSsid failed: empty string"); 283 return false; 284 } 285 if (ssid.startsWith("\"")) { 286 // UTF-8 SSID string 287 byte[] ssidBytes = ssid.getBytes(StandardCharsets.UTF_8); 288 if (ssidBytes.length < SSID_UTF_8_MIN_LEN) { 289 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too small: " 290 + ssidBytes.length); 291 return false; 292 } 293 if (ssidBytes.length > SSID_UTF_8_MAX_LEN) { 294 Log.e(TAG, "validateSsid failed: utf-8 ssid string size too large: " 295 + ssidBytes.length); 296 return false; 297 } 298 } else { 299 // HEX SSID string 300 if (ssid.length() < SSID_HEX_MIN_LEN) { 301 Log.e(TAG, "validateSsid failed: hex string size too small: " + ssid.length()); 302 return false; 303 } 304 if (ssid.length() > SSID_HEX_MAX_LEN) { 305 Log.e(TAG, "validateSsid failed: hex string size too large: " + ssid.length()); 306 return false; 307 } 308 } 309 try { 310 NativeUtil.decodeSsid(ssid); 311 } catch (IllegalArgumentException e) { 312 Log.e(TAG, "validateSsid failed: malformed string: " + ssid); 313 return false; 314 } 315 return true; 316 } 317 318 private static boolean validatePsk(String psk, boolean isAdd) { 319 if (isAdd) { 320 if (psk == null) { 321 Log.e(TAG, "validatePsk: null string"); 322 return false; 323 } 324 } else { 325 if (psk == null) { 326 // This is an update, so the psk can be null if that is not being changed. 327 return true; 328 } else if (psk.equals(PASSWORD_MASK)) { 329 // This is an update, so the app might have returned back the masked password, let 330 // it thru. WifiConfigManager will handle it. 331 return true; 332 } 333 } 334 if (psk.isEmpty()) { 335 Log.e(TAG, "validatePsk failed: empty string"); 336 return false; 337 } 338 if (psk.startsWith("\"")) { 339 // ASCII PSK string 340 byte[] pskBytes = psk.getBytes(StandardCharsets.US_ASCII); 341 if (pskBytes.length < PSK_ASCII_MIN_LEN) { 342 Log.e(TAG, "validatePsk failed: ascii string size too small: " + pskBytes.length); 343 return false; 344 } 345 if (pskBytes.length > PSK_ASCII_MAX_LEN) { 346 Log.e(TAG, "validatePsk failed: ascii string size too large: " + pskBytes.length); 347 return false; 348 } 349 } else { 350 // HEX PSK string 351 if (psk.length() != PSK_HEX_LEN) { 352 Log.e(TAG, "validatePsk failed: hex string size mismatch: " + psk.length()); 353 return false; 354 } 355 } 356 try { 357 NativeUtil.hexOrQuotedStringToBytes(psk); 358 } catch (IllegalArgumentException e) { 359 Log.e(TAG, "validatePsk failed: malformed string: " + psk); 360 return false; 361 } 362 return true; 363 } 364 365 private static boolean validateBitSet(BitSet bitSet, int validValuesLength) { 366 if (bitSet == null) return false; 367 BitSet clonedBitset = (BitSet) bitSet.clone(); 368 clonedBitset.clear(0, validValuesLength); 369 return clonedBitset.isEmpty(); 370 } 371 372 private static boolean validateBitSets(WifiConfiguration config) { 373 // 1. Check |allowedKeyManagement|. 374 if (!validateBitSet(config.allowedKeyManagement, 375 WifiConfiguration.KeyMgmt.strings.length)) { 376 Log.e(TAG, "validateBitsets failed: invalid allowedKeyManagement bitset " 377 + config.allowedKeyManagement); 378 return false; 379 } 380 // 2. Check |allowedProtocols|. 381 if (!validateBitSet(config.allowedProtocols, 382 WifiConfiguration.Protocol.strings.length)) { 383 Log.e(TAG, "validateBitsets failed: invalid allowedProtocols bitset " 384 + config.allowedProtocols); 385 return false; 386 } 387 // 3. Check |allowedAuthAlgorithms|. 388 if (!validateBitSet(config.allowedAuthAlgorithms, 389 WifiConfiguration.AuthAlgorithm.strings.length)) { 390 Log.e(TAG, "validateBitsets failed: invalid allowedAuthAlgorithms bitset " 391 + config.allowedAuthAlgorithms); 392 return false; 393 } 394 // 4. Check |allowedGroupCiphers|. 395 if (!validateBitSet(config.allowedGroupCiphers, 396 WifiConfiguration.GroupCipher.strings.length)) { 397 Log.e(TAG, "validateBitsets failed: invalid allowedGroupCiphers bitset " 398 + config.allowedGroupCiphers); 399 return false; 400 } 401 // 5. Check |allowedPairwiseCiphers|. 402 if (!validateBitSet(config.allowedPairwiseCiphers, 403 WifiConfiguration.PairwiseCipher.strings.length)) { 404 Log.e(TAG, "validateBitsets failed: invalid allowedPairwiseCiphers bitset " 405 + config.allowedPairwiseCiphers); 406 return false; 407 } 408 return true; 409 } 410 411 private static boolean validateKeyMgmt(BitSet keyMgmnt) { 412 if (keyMgmnt.cardinality() > 1) { 413 if (keyMgmnt.cardinality() != 2) { 414 Log.e(TAG, "validateKeyMgmt failed: cardinality != 2"); 415 return false; 416 } 417 if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_EAP)) { 418 Log.e(TAG, "validateKeyMgmt failed: not WPA_EAP"); 419 return false; 420 } 421 if (!keyMgmnt.get(WifiConfiguration.KeyMgmt.IEEE8021X) 422 && !keyMgmnt.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 423 Log.e(TAG, "validateKeyMgmt failed: not PSK or 8021X"); 424 return false; 425 } 426 } 427 return true; 428 } 429 430 private static boolean validateIpConfiguration(IpConfiguration ipConfig) { 431 if (ipConfig == null) { 432 Log.e(TAG, "validateIpConfiguration failed: null IpConfiguration"); 433 return false; 434 } 435 if (ipConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) { 436 StaticIpConfiguration staticIpConfig = ipConfig.getStaticIpConfiguration(); 437 if (staticIpConfig == null) { 438 Log.e(TAG, "validateIpConfiguration failed: null StaticIpConfiguration"); 439 return false; 440 } 441 if (staticIpConfig.ipAddress == null) { 442 Log.e(TAG, "validateIpConfiguration failed: null static ip Address"); 443 return false; 444 } 445 } 446 return true; 447 } 448 449 /** 450 * Enums to specify if the provided config is being validated for add or update. 451 */ 452 public static final boolean VALIDATE_FOR_ADD = true; 453 public static final boolean VALIDATE_FOR_UPDATE = false; 454 455 /** 456 * Validate the configuration received from an external application. 457 * 458 * This method checks for the following parameters: 459 * 1. {@link WifiConfiguration#SSID} 460 * 2. {@link WifiConfiguration#preSharedKey} 461 * 3. {@link WifiConfiguration#allowedKeyManagement} 462 * 4. {@link WifiConfiguration#allowedProtocols} 463 * 5. {@link WifiConfiguration#allowedAuthAlgorithms} 464 * 6. {@link WifiConfiguration#allowedGroupCiphers} 465 * 7. {@link WifiConfiguration#allowedPairwiseCiphers} 466 * 8. {@link WifiConfiguration#getIpConfiguration()} 467 * 468 * @param config {@link WifiConfiguration} received from an external application. 469 * @param isAdd {@link #VALIDATE_FOR_ADD} to indicate a network config received for an add, 470 * {@link #VALIDATE_FOR_UPDATE} for a network config received for an update. 471 * These 2 cases need to be handled differently because the config received for an 472 * update could contain only the fields that are being changed. 473 * @return true if the parameters are valid, false otherwise. 474 */ 475 public static boolean validate(WifiConfiguration config, boolean isAdd) { 476 if (!validateSsid(config.SSID, isAdd)) { 477 return false; 478 } 479 if (!validateBitSets(config)) { 480 return false; 481 } 482 if (!validateKeyMgmt(config.allowedKeyManagement)) { 483 return false; 484 } 485 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK) 486 && !validatePsk(config.preSharedKey, isAdd)) { 487 return false; 488 } 489 if (!validateIpConfiguration(config.getIpConfiguration())) { 490 return false; 491 } 492 // TBD: Validate some enterprise params as well in the future here. 493 return true; 494 } 495 496 /** 497 * Check if the provided two networks are the same. 498 * Note: This does not check if network selection BSSID's are the same. 499 * 500 * @param config Configuration corresponding to a network. 501 * @param config1 Configuration corresponding to another network. 502 * 503 * @return true if |config| and |config1| are the same network. 504 * false otherwise. 505 */ 506 public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) { 507 if (config == null && config1 == null) { 508 return true; 509 } 510 if (config == null || config1 == null) { 511 return false; 512 } 513 if (config.networkId != config1.networkId) { 514 return false; 515 } 516 if (!Objects.equals(config.SSID, config1.SSID)) { 517 return false; 518 } 519 if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) { 520 return false; 521 } 522 return true; 523 } 524 525 /** 526 * Create a PnoNetwork object from the provided WifiConfiguration. 527 * 528 * @param config Configuration corresponding to the network. 529 * @return PnoNetwork object corresponding to the network. 530 */ 531 public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork( 532 WifiConfiguration config) { 533 WifiScanner.PnoSettings.PnoNetwork pnoNetwork = 534 new WifiScanner.PnoSettings.PnoNetwork(config.SSID); 535 if (config.hiddenSSID) { 536 pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN; 537 } 538 pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND; 539 pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND; 540 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 541 pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK; 542 } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) 543 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) { 544 pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL; 545 } else { 546 pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN; 547 } 548 return pnoNetwork; 549 } 550 551 552 /** 553 * General WifiConfiguration list sorting algorithm: 554 * 1, Place the fully enabled networks first. 555 * 2. Next place all the temporarily disabled networks. 556 * 3. Place the permanently disabled networks last (Permanently disabled networks are removed 557 * before WifiConfigManager uses this comparator today!). 558 * 559 * Among the networks with the same status, sort them in the order determined by the return of 560 * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method 561 * implementation. 562 */ 563 public abstract static class WifiConfigurationComparator implements 564 Comparator<WifiConfiguration> { 565 private static final int ENABLED_NETWORK_SCORE = 3; 566 private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2; 567 private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1; 568 569 @Override 570 public int compare(WifiConfiguration a, WifiConfiguration b) { 571 int configAScore = getNetworkStatusScore(a); 572 int configBScore = getNetworkStatusScore(b); 573 if (configAScore == configBScore) { 574 return compareNetworksWithSameStatus(a, b); 575 } else { 576 return Integer.compare(configBScore, configAScore); 577 } 578 } 579 580 // This needs to be implemented by the connected/disconnected PNO list comparator. 581 abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b); 582 583 /** 584 * Returns an integer representing a score for each configuration. The scores are assigned 585 * based on the status of the configuration. The scores are assigned according to the order: 586 * Fully enabled network > Temporarily disabled network > Permanently disabled network. 587 */ 588 private int getNetworkStatusScore(WifiConfiguration config) { 589 if (config.getNetworkSelectionStatus().isNetworkEnabled()) { 590 return ENABLED_NETWORK_SCORE; 591 } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) { 592 return TEMPORARY_DISABLED_NETWORK_SCORE; 593 } else { 594 return PERMANENTLY_DISABLED_NETWORK_SCORE; 595 } 596 } 597 } 598 } 599