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