1 /* 2 * Copyright (C) 2021 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 android.net.wifi.util; 18 19 import static android.net.wifi.ScanResult.FLAG_PASSPOINT_NETWORK; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.net.MacAddress; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.SecurityParams; 26 import android.net.wifi.WifiConfiguration; 27 import android.util.Log; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.List; 34 /** 35 * Scan result utility for any {@link ScanResult} related operations. 36 * Currently contains: 37 * > Helper methods to identify the encryption of a ScanResult. 38 * @hide 39 */ 40 public class ScanResultUtil { 41 private static final String TAG = "ScanResultUtil"; ScanResultUtil()42 private ScanResultUtil() { /* not constructable */ } 43 44 /** 45 * Helper method to check if the provided |scanResult| corresponds to a PSK network or not. 46 * This checks if the provided capabilities string contains PSK encryption type or not. 47 */ isScanResultForPskNetwork(@onNull ScanResult scanResult)48 public static boolean isScanResultForPskNetwork(@NonNull ScanResult scanResult) { 49 return scanResult.capabilities.contains("PSK"); 50 } 51 52 /** 53 * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not. 54 * This checks if the provided capabilities string contains PSK encryption type or not. 55 */ isScanResultForWapiPskNetwork(@onNull ScanResult scanResult)56 public static boolean isScanResultForWapiPskNetwork(@NonNull ScanResult scanResult) { 57 return scanResult.capabilities.contains("WAPI-PSK"); 58 } 59 60 /** 61 * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT 62 * network or not. 63 * This checks if the provided capabilities string contains PSK encryption type or not. 64 */ isScanResultForWapiCertNetwork(@onNull ScanResult scanResult)65 public static boolean isScanResultForWapiCertNetwork(@NonNull ScanResult scanResult) { 66 return scanResult.capabilities.contains("WAPI-CERT"); 67 } 68 69 /** 70 * Helper method to check if the provided |scanResult| corresponds to a EAP network or not. 71 * This checks these conditions: 72 * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS. 73 * - Not a WPA3 Enterprise only network. 74 * - Not a WPA3 Enterprise transition network. 75 */ isScanResultForEapNetwork(@onNull ScanResult scanResult)76 public static boolean isScanResultForEapNetwork(@NonNull ScanResult scanResult) { 77 return (scanResult.capabilities.contains("EAP/SHA1") 78 || scanResult.capabilities.contains("EAP/SHA256") 79 || scanResult.capabilities.contains("FT/EAP") 80 || scanResult.capabilities.contains("EAP-FILS")) 81 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) 82 && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult); 83 } 84 isScanResultForPmfMandatoryNetwork(@onNull ScanResult scanResult)85 private static boolean isScanResultForPmfMandatoryNetwork(@NonNull ScanResult scanResult) { 86 return scanResult.capabilities.contains("[MFPR]"); 87 } 88 isScanResultForPmfCapableNetwork(@onNull ScanResult scanResult)89 private static boolean isScanResultForPmfCapableNetwork(@NonNull ScanResult scanResult) { 90 return scanResult.capabilities.contains("[MFPC]"); 91 } 92 93 /** 94 * Helper method to check if the provided |scanResult| corresponds to a Passpoint R1/R2 95 * network or not. 96 * Passpoint R1/R2 requirements: 97 * - WPA2 Enterprise network. 98 * - interworking bit is set. 99 * - HotSpot Release presents. 100 */ isScanResultForPasspointR1R2Network(@onNull ScanResult scanResult)101 public static boolean isScanResultForPasspointR1R2Network(@NonNull ScanResult scanResult) { 102 if (!isScanResultForEapNetwork(scanResult)) return false; 103 104 return scanResult.isPasspointNetwork(); 105 } 106 107 /** 108 * Helper method to check if the provided |scanResult| corresponds to a Passpoint R3 109 * network or not. 110 * Passpoint R3 requirements: 111 * - Must be WPA2 Enterprise network, WPA3 Enterprise network, 112 * or WPA3 Enterprise 192-bit mode network. 113 * - interworking bit is set. 114 * - HotSpot Release presents. 115 * - PMF is mandatory. 116 */ isScanResultForPasspointR3Network(@onNull ScanResult scanResult)117 public static boolean isScanResultForPasspointR3Network(@NonNull ScanResult scanResult) { 118 if (!isScanResultForEapNetwork(scanResult) 119 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) 120 && !isScanResultForEapSuiteBNetwork(scanResult)) { 121 return false; 122 } 123 if (!isScanResultForPmfMandatoryNetwork(scanResult)) return false; 124 125 return scanResult.isPasspointNetwork(); 126 } 127 128 /** 129 * Helper method to check if the provided |scanResult| corresponds to 130 * a WPA3 Enterprise transition network or not. 131 * 132 * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification 133 * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites. 134 * - Not enable WPA1 version 1, WEP, and TKIP. 135 * - Management Frame Protection Capable is set. 136 * - Management Frame Protection Required is not set. 137 */ isScanResultForWpa3EnterpriseTransitionNetwork( @onNull ScanResult scanResult)138 public static boolean isScanResultForWpa3EnterpriseTransitionNetwork( 139 @NonNull ScanResult scanResult) { 140 return scanResult.capabilities.contains("EAP/SHA1") 141 && scanResult.capabilities.contains("EAP/SHA256") 142 && scanResult.capabilities.contains("RSN") 143 && !scanResult.capabilities.contains("WEP") 144 && !scanResult.capabilities.contains("TKIP") 145 && !isScanResultForPmfMandatoryNetwork(scanResult) 146 && isScanResultForPmfCapableNetwork(scanResult); 147 } 148 149 /** 150 * Helper method to check if the provided |scanResult| corresponds to 151 * a WPA3 Enterprise only network or not. 152 * 153 * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification 154 * - Enable at least EAP/SHA256 AKM suite. 155 * - Not enable EAP/SHA1 AKM suite. 156 * - Not enable WPA1 version 1, WEP, and TKIP. 157 * - Management Frame Protection Capable is set. 158 * - Management Frame Protection Required is set. 159 */ isScanResultForWpa3EnterpriseOnlyNetwork(@onNull ScanResult scanResult)160 public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(@NonNull ScanResult scanResult) { 161 return scanResult.capabilities.contains("EAP/SHA256") 162 && !scanResult.capabilities.contains("EAP/SHA1") 163 && scanResult.capabilities.contains("RSN") 164 && !scanResult.capabilities.contains("WEP") 165 && !scanResult.capabilities.contains("TKIP") 166 && isScanResultForPmfMandatoryNetwork(scanResult) 167 && isScanResultForPmfCapableNetwork(scanResult); 168 } 169 170 /** 171 * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit 172 * mode network or not. 173 * This checks if the provided capabilities comply these conditions: 174 * - Enable SUITE-B-192 AKM. 175 * - Not enable EAP/SHA1 AKM suite. 176 * - Not enable WPA1 version 1, WEP, and TKIP. 177 * - Management Frame Protection Required is set. 178 */ isScanResultForEapSuiteBNetwork(@onNull ScanResult scanResult)179 public static boolean isScanResultForEapSuiteBNetwork(@NonNull ScanResult scanResult) { 180 return scanResult.capabilities.contains("SUITE_B_192") 181 && scanResult.capabilities.contains("RSN") 182 && !scanResult.capabilities.contains("WEP") 183 && !scanResult.capabilities.contains("TKIP") 184 && isScanResultForPmfMandatoryNetwork(scanResult); 185 } 186 187 /** 188 * Helper method to check if the provided |scanResult| corresponds to a WEP network or not. 189 * This checks if the provided capabilities string contains WEP encryption type or not. 190 */ isScanResultForWepNetwork(@onNull ScanResult scanResult)191 public static boolean isScanResultForWepNetwork(@NonNull ScanResult scanResult) { 192 return scanResult.capabilities.contains("WEP"); 193 } 194 195 /** 196 * Helper method to check if the provided |scanResult| corresponds to OWE network. 197 * This checks if the provided capabilities string contains OWE or not. 198 */ isScanResultForOweNetwork(@onNull ScanResult scanResult)199 public static boolean isScanResultForOweNetwork(@NonNull ScanResult scanResult) { 200 return scanResult.capabilities.contains("OWE"); 201 } 202 203 /** 204 * Helper method to check if the provided |scanResult| corresponds to OWE transition network. 205 * This checks if the provided capabilities string contains OWE_TRANSITION or not. 206 */ isScanResultForOweTransitionNetwork(@onNull ScanResult scanResult)207 public static boolean isScanResultForOweTransitionNetwork(@NonNull ScanResult scanResult) { 208 return scanResult.capabilities.contains("OWE_TRANSITION"); 209 } 210 211 /** 212 * Helper method to check if the provided |scanResult| corresponds to SAE network. 213 * This checks if the provided capabilities string contains SAE or not. 214 */ isScanResultForSaeNetwork(@onNull ScanResult scanResult)215 public static boolean isScanResultForSaeNetwork(@NonNull ScanResult scanResult) { 216 return scanResult.capabilities.contains("SAE"); 217 } 218 219 /** 220 * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition 221 * network. This checks if the provided capabilities string contains both PSK and SAE or not. 222 */ isScanResultForPskSaeTransitionNetwork(@onNull ScanResult scanResult)223 public static boolean isScanResultForPskSaeTransitionNetwork(@NonNull ScanResult scanResult) { 224 return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE"); 225 } 226 227 /** 228 * Helper method to check if the provided |scanResult| corresponds to FILS SHA256 network. 229 * This checks if the provided capabilities string contains FILS-SHA256 or not. 230 */ isScanResultForFilsSha256Network(@onNull ScanResult scanResult)231 public static boolean isScanResultForFilsSha256Network(@NonNull ScanResult scanResult) { 232 return scanResult.capabilities.contains("FILS-SHA256"); 233 } 234 235 /** 236 * Helper method to check if the provided |scanResult| corresponds to FILS SHA384 network. 237 * This checks if the provided capabilities string contains FILS-SHA384 or not. 238 */ isScanResultForFilsSha384Network(@onNull ScanResult scanResult)239 public static boolean isScanResultForFilsSha384Network(@NonNull ScanResult scanResult) { 240 return scanResult.capabilities.contains("FILS-SHA384"); 241 } 242 243 /** 244 * Helper method to check if the provided |scanResult| corresponds to DPP network. 245 * This checks if the provided capabilities string contains DPP or not. 246 */ isScanResultForDppNetwork(@onNull ScanResult scanResult)247 public static boolean isScanResultForDppNetwork(@NonNull ScanResult scanResult) { 248 return scanResult.capabilities.contains("DPP"); 249 } 250 251 /** 252 * Helper method to check if the provided |scanResult| corresponds to an unknown amk network. 253 * This checks if the provided capabilities string contains ? or not. 254 */ isScanResultForUnknownAkmNetwork(@onNull ScanResult scanResult)255 public static boolean isScanResultForUnknownAkmNetwork(@NonNull ScanResult scanResult) { 256 return scanResult.capabilities.contains("?"); 257 } 258 259 /** 260 * Helper method to check if the provided |scanResult| corresponds to an open network or not. 261 * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE 262 * EAP, or unknown encryption types or not. 263 */ isScanResultForOpenNetwork(@onNull ScanResult scanResult)264 public static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) { 265 return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult) 266 || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult) 267 || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult) 268 || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) 269 || isScanResultForWapiPskNetwork(scanResult) 270 || isScanResultForWapiCertNetwork(scanResult) 271 || isScanResultForEapSuiteBNetwork(scanResult) 272 || isScanResultForDppNetwork(scanResult) 273 || isScanResultForUnknownAkmNetwork(scanResult))); 274 } 275 276 /** 277 * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in 278 * WifiConfiguration object. 279 */ 280 @VisibleForTesting createQuotedSsid(@ullable String ssid)281 public static @NonNull String createQuotedSsid(@Nullable String ssid) { 282 return "\"" + ssid + "\""; 283 } 284 285 /** 286 * Creates a network configuration object using the provided |scanResult|. 287 */ createNetworkFromScanResult( @onNull ScanResult scanResult)288 public static @Nullable WifiConfiguration createNetworkFromScanResult( 289 @NonNull ScanResult scanResult) { 290 WifiConfiguration config = new WifiConfiguration(); 291 config.SSID = createQuotedSsid(scanResult.SSID); 292 List<SecurityParams> list = generateSecurityParamsListFromScanResult(scanResult); 293 if (list.isEmpty()) { 294 return null; 295 } 296 config.setSecurityParams(list); 297 return config; 298 } 299 300 /** 301 * Generate security params from the scan result. 302 * @param scanResult the scan result to be checked. 303 * @return a list of security params. If no known security params, return an empty list. 304 */ generateSecurityParamsListFromScanResult( @onNull ScanResult scanResult)305 public static @NonNull List<SecurityParams> generateSecurityParamsListFromScanResult( 306 @NonNull ScanResult scanResult) { 307 List<SecurityParams> list = new ArrayList<>(); 308 309 // Open network & its upgradable types 310 if (ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult)) { 311 list.add(SecurityParams.createSecurityParamsBySecurityType( 312 WifiConfiguration.SECURITY_TYPE_OPEN)); 313 list.add(SecurityParams.createSecurityParamsBySecurityType( 314 WifiConfiguration.SECURITY_TYPE_OWE)); 315 return list; 316 } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) { 317 list.add(SecurityParams.createSecurityParamsBySecurityType( 318 WifiConfiguration.SECURITY_TYPE_OWE)); 319 return list; 320 } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { 321 list.add(SecurityParams.createSecurityParamsBySecurityType( 322 WifiConfiguration.SECURITY_TYPE_OPEN)); 323 return list; 324 } 325 326 // WEP network which has no upgradable type 327 if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { 328 list.add(SecurityParams.createSecurityParamsBySecurityType( 329 WifiConfiguration.SECURITY_TYPE_WEP)); 330 return list; 331 } 332 333 // WAPI PSK network which has no upgradable type 334 if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) { 335 list.add(SecurityParams.createSecurityParamsBySecurityType( 336 WifiConfiguration.SECURITY_TYPE_WAPI_PSK)); 337 return list; 338 } 339 340 // WAPI CERT network which has no upgradable type 341 if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) { 342 list.add(SecurityParams.createSecurityParamsBySecurityType( 343 WifiConfiguration.SECURITY_TYPE_WAPI_CERT)); 344 return list; 345 } 346 347 // WPA2 personal network & its upgradable types 348 if (ScanResultUtil.isScanResultForPskNetwork(scanResult) 349 && ScanResultUtil.isScanResultForSaeNetwork(scanResult)) { 350 list.add(SecurityParams.createSecurityParamsBySecurityType( 351 WifiConfiguration.SECURITY_TYPE_PSK)); 352 list.add(SecurityParams.createSecurityParamsBySecurityType( 353 WifiConfiguration.SECURITY_TYPE_SAE)); 354 return list; 355 } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { 356 list.add(SecurityParams.createSecurityParamsBySecurityType( 357 WifiConfiguration.SECURITY_TYPE_PSK)); 358 return list; 359 } else if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) { 360 list.add(SecurityParams.createSecurityParamsBySecurityType( 361 WifiConfiguration.SECURITY_TYPE_SAE)); 362 return list; 363 } else if (ScanResultUtil.isScanResultForDppNetwork(scanResult)) { 364 list.add(SecurityParams.createSecurityParamsBySecurityType( 365 WifiConfiguration.SECURITY_TYPE_DPP)); 366 return list; 367 } 368 369 // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types 370 if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) { 371 list.add(SecurityParams.createSecurityParamsBySecurityType( 372 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)); 373 } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) { 374 list.add(SecurityParams.createSecurityParamsBySecurityType( 375 WifiConfiguration.SECURITY_TYPE_EAP)); 376 list.add(SecurityParams.createSecurityParamsBySecurityType( 377 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)); 378 } else if (ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) { 379 list.add(SecurityParams.createSecurityParamsBySecurityType( 380 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)); 381 } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { 382 list.add(SecurityParams.createSecurityParamsBySecurityType( 383 WifiConfiguration.SECURITY_TYPE_EAP)); 384 } 385 // An Enterprise network might be a Passpoint network as well. 386 // R3 network might be also a valid R1/R2 network. 387 if (isScanResultForPasspointR1R2Network(scanResult)) { 388 list.add(SecurityParams.createSecurityParamsBySecurityType( 389 WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2)); 390 } 391 if (isScanResultForPasspointR3Network(scanResult)) { 392 list.add(SecurityParams.createSecurityParamsBySecurityType( 393 WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3)); 394 } 395 return list; 396 } 397 398 /** 399 * Dump the provided scan results list to |pw|. 400 */ dumpScanResults(@onNull PrintWriter pw, @Nullable List<ScanResult> scanResults, long nowMs)401 public static void dumpScanResults(@NonNull PrintWriter pw, 402 @Nullable List<ScanResult> scanResults, long nowMs) { 403 if (scanResults != null && scanResults.size() != 0) { 404 pw.println(" BSSID Frequency RSSI Age(sec) SSID " 405 + " Flags"); 406 for (ScanResult r : scanResults) { 407 long timeStampMs = r.timestamp / 1000; 408 String age; 409 if (timeStampMs <= 0) { 410 age = "___?___"; 411 } else if (nowMs < timeStampMs) { 412 age = " 0.000"; 413 } else if (timeStampMs < nowMs - 1000000) { 414 age = ">1000.0"; 415 } else { 416 age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0); 417 } 418 String ssid = r.SSID == null ? "" : r.SSID; 419 String rssiInfo = ""; 420 int numRadioChainInfos = r.radioChainInfos == null ? 0 : r.radioChainInfos.length; 421 if (numRadioChainInfos == 1) { 422 rssiInfo = String.format("%5d(%1d:%3d) ", r.level, 423 r.radioChainInfos[0].id, r.radioChainInfos[0].level); 424 } else if (numRadioChainInfos == 2) { 425 rssiInfo = String.format("%5d(%1d:%3d/%1d:%3d)", r.level, 426 r.radioChainInfos[0].id, r.radioChainInfos[0].level, 427 r.radioChainInfos[1].id, r.radioChainInfos[1].level); 428 } else { 429 rssiInfo = String.format("%9d ", r.level); 430 } 431 if ((r.flags & FLAG_PASSPOINT_NETWORK) 432 == FLAG_PASSPOINT_NETWORK) { 433 r.capabilities += "[PASSPOINT]"; 434 } 435 pw.printf(" %17s %9d %18s %7s %-32s %s\n", 436 r.BSSID, 437 r.frequency, 438 rssiInfo, 439 age, 440 String.format("%1.32s", ssid), 441 r.capabilities); 442 } 443 } 444 } 445 446 /** 447 * Check if ScanResult list is valid. 448 */ validateScanResultList(@ullable List<ScanResult> scanResults)449 public static boolean validateScanResultList(@Nullable List<ScanResult> scanResults) { 450 if (scanResults == null || scanResults.isEmpty()) { 451 Log.w(TAG, "Empty or null ScanResult list"); 452 return false; 453 } 454 for (ScanResult scanResult : scanResults) { 455 if (!validate(scanResult)) { 456 Log.w(TAG, "Invalid ScanResult: " + scanResult); 457 return false; 458 } 459 } 460 return true; 461 } 462 validate(@ullable ScanResult scanResult)463 private static boolean validate(@Nullable ScanResult scanResult) { 464 return scanResult != null && scanResult.SSID != null 465 && scanResult.capabilities != null && scanResult.BSSID != null; 466 } 467 468 /** 469 * Redact bytes from a bssid. 470 */ redactBssid(MacAddress bssid, int numRedactedOctets)471 public static String redactBssid(MacAddress bssid, int numRedactedOctets) { 472 if (bssid == null) { 473 return ""; 474 } 475 StringBuilder redactedBssid = new StringBuilder(); 476 byte[] bssidBytes = bssid.toByteArray(); 477 478 if (numRedactedOctets < 0 || numRedactedOctets > 6) { 479 // Reset to default if passed value is invalid. 480 numRedactedOctets = 4; 481 } 482 for (int i = 0; i < 6; i++) { 483 if (i < numRedactedOctets) { 484 redactedBssid.append("xx"); 485 } else { 486 redactedBssid.append(String.format("%02X", bssidBytes[i])); 487 } 488 if (i != 5) { 489 redactedBssid.append(":"); 490 } 491 } 492 return redactedBssid.toString(); 493 } 494 } 495