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