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