/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.wifi.ScanResult; import android.net.wifi.SecurityParams; import android.net.wifi.WifiConfiguration; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.ScanDetail; import com.android.server.wifi.hotspot2.NetworkDetail; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * Scan result utility for any {@link ScanResult} related operations. * Currently contains: * > Helper method for converting a ScanResult to a ScanDetail. * Only fields that are supported in ScanResult are copied. * > Helper methods to identify the encryption of a ScanResult. */ public class ScanResultUtil { private static final String TAG = "ScanResultUtil"; private ScanResultUtil() { /* not constructable */ } /** * This method should only be used when the informationElements field in the provided scan * result is filled in with the IEs from the beacon. */ public static ScanDetail toScanDetail(ScanResult scanResult) { ScanResult.InformationElement[] ieArray = (null != scanResult.informationElements) ? scanResult.informationElements : new ScanResult.InformationElement[0]; NetworkDetail networkDetail = new NetworkDetail(scanResult.BSSID, ieArray, scanResult.anqpLines, scanResult.frequency); return new ScanDetail(scanResult, networkDetail); } /** * Helper method to check if the provided |scanResult| corresponds to a PSK network or not. * This checks if the provided capabilities string contains PSK encryption type or not. */ public static boolean isScanResultForPskNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("PSK"); } /** * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not. * This checks if the provided capabilities string contains PSK encryption type or not. */ public static boolean isScanResultForWapiPskNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("WAPI-PSK"); } /** * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT * network or not. * This checks if the provided capabilities string contains PSK encryption type or not. */ public static boolean isScanResultForWapiCertNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("WAPI-CERT"); } /** * Helper method to check if the provided |scanResult| corresponds to a EAP network or not. * This checks these conditions: * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS. * - Not a WPA3 Enterprise only network. * - Not a WPA3 Enterprise transition network. */ public static boolean isScanResultForEapNetwork(ScanResult scanResult) { return (scanResult.capabilities.contains("EAP/SHA1") || scanResult.capabilities.contains("EAP/SHA256") || scanResult.capabilities.contains("FT/EAP") || scanResult.capabilities.contains("EAP-FILS")) && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult); } private static boolean isScanResultForPmfMandatoryNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("[MFPR]"); } private static boolean isScanResultForPmfCapableNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("[MFPC]"); } /** * Helper method to check if the provided |scanResult| corresponds to a Passpoint R1/R2 * network or not. * Passpoint R1/R2 requirements: * - WPA2 Enterprise network. * - interworking bit is set. * - HotSpot Release presents. */ public static boolean isScanResultForPasspointR1R2Network(ScanResult scanResult) { if (!isScanResultForEapNetwork(scanResult)) return false; ScanDetail detail = toScanDetail(scanResult); if (!detail.getNetworkDetail().isInterworking()) return false; return null != detail.getNetworkDetail().getHSRelease(); } /** * Helper method to check if the provided |scanResult| corresponds to a Passpoint R3 * network or not. * Passpoint R3 requirements: * - Must be WPA2 Enterprise network, WPA3 Enterprise network, * or WPA3 Enterprise 192-bit mode network. * - interworking bit is set. * - HotSpot Release presents. * - PMF is mandatory. */ public static boolean isScanResultForPasspointR3Network(ScanResult scanResult) { if (!isScanResultForEapNetwork(scanResult) && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) && !isScanResultForEapSuiteBNetwork(scanResult)) { return false; } if (!isScanResultForPmfMandatoryNetwork(scanResult)) return false; ScanDetail detail = toScanDetail(scanResult); if (!detail.getNetworkDetail().isInterworking()) return false; return null != detail.getNetworkDetail().getHSRelease(); } /** * Helper method to check if the provided |scanResult| corresponds to * a WPA3 Enterprise transition network or not. * * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites. * - Not enable WPA1 version 1, WEP, and TKIP. * - Management Frame Protection Capable is set. * - Management Frame Protection Required is not set. */ public static boolean isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("EAP/SHA1") && scanResult.capabilities.contains("EAP/SHA256") && scanResult.capabilities.contains("RSN") && !scanResult.capabilities.contains("WEP") && !scanResult.capabilities.contains("TKIP") && !isScanResultForPmfMandatoryNetwork(scanResult) && isScanResultForPmfCapableNetwork(scanResult); } /** * Helper method to check if the provided |scanResult| corresponds to * a WPA3 Enterprise only network or not. * * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification * - Enable at least EAP/SHA256 AKM suite. * - Not enable EAP/SHA1 AKM suite. * - Not enable WPA1 version 1, WEP, and TKIP. * - Management Frame Protection Capable is set. * - Management Frame Protection Required is set. */ public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("EAP/SHA256") && !scanResult.capabilities.contains("EAP/SHA1") && scanResult.capabilities.contains("RSN") && !scanResult.capabilities.contains("WEP") && !scanResult.capabilities.contains("TKIP") && isScanResultForPmfMandatoryNetwork(scanResult) && isScanResultForPmfCapableNetwork(scanResult); } /** * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit * mode network or not. * This checks if the provided capabilities comply these conditions: * - Enable SUITE-B-192 AKM. * - Not enable EAP/SHA1 AKM suite. * - Not enable WPA1 version 1, WEP, and TKIP. * - Management Frame Protection Required is set. */ public static boolean isScanResultForEapSuiteBNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("SUITE_B_192") && scanResult.capabilities.contains("RSN") && !scanResult.capabilities.contains("WEP") && !scanResult.capabilities.contains("TKIP") && isScanResultForPmfMandatoryNetwork(scanResult); } /** * Helper method to check if the provided |scanResult| corresponds to a WEP network or not. * This checks if the provided capabilities string contains WEP encryption type or not. */ public static boolean isScanResultForWepNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("WEP"); } /** * Helper method to check if the provided |scanResult| corresponds to OWE network. * This checks if the provided capabilities string contains OWE or not. */ public static boolean isScanResultForOweNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("OWE"); } /** * Helper method to check if the provided |scanResult| corresponds to OWE transition network. * This checks if the provided capabilities string contains OWE_TRANSITION or not. */ public static boolean isScanResultForOweTransitionNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("OWE_TRANSITION"); } /** * Helper method to check if the provided |scanResult| corresponds to SAE network. * This checks if the provided capabilities string contains SAE or not. */ public static boolean isScanResultForSaeNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("SAE"); } /** * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition * network. This checks if the provided capabilities string contains both PSK and SAE or not. */ public static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE"); } /** * Helper method to check if the provided |scanResult| corresponds to FILS SHA256 network. * This checks if the provided capabilities string contains FILS-SHA256 or not. */ public static boolean isScanResultForFilsSha256Network(ScanResult scanResult) { return scanResult.capabilities.contains("FILS-SHA256"); } /** * Helper method to check if the provided |scanResult| corresponds to FILS SHA384 network. * This checks if the provided capabilities string contains FILS-SHA384 or not. */ public static boolean isScanResultForFilsSha384Network(ScanResult scanResult) { return scanResult.capabilities.contains("FILS-SHA384"); } /** * Helper method to check if the provided |scanResult| corresponds to an unknown amk network. * This checks if the provided capabilities string contains ? or not. */ public static boolean isScanResultForUnknownAkmNetwork(ScanResult scanResult) { return scanResult.capabilities.contains("?"); } /** * Helper method to check if the provided |scanResult| corresponds to an open network or not. * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE * EAP, or unknown encryption types or not. */ public static boolean isScanResultForOpenNetwork(ScanResult scanResult) { return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult) || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult) || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult) || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult) || isScanResultForWapiPskNetwork(scanResult) || isScanResultForWapiCertNetwork(scanResult) || isScanResultForEapSuiteBNetwork(scanResult) || isScanResultForUnknownAkmNetwork(scanResult))); } /** * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in * WifiConfiguration object. */ @VisibleForTesting public static String createQuotedSSID(String ssid) { return "\"" + ssid + "\""; } /** * Creates a network configuration object using the provided |scanResult|. * This is used to create ephemeral network configurations. */ public static @Nullable WifiConfiguration createNetworkFromScanResult(ScanResult scanResult) { WifiConfiguration config = new WifiConfiguration(); config.SSID = createQuotedSSID(scanResult.SSID); List list = generateSecurityParamsListFromScanResult(scanResult); if (list.isEmpty()) { return null; } config.setSecurityParams(list); return config; } /** * Generate security params from the scan result. * @param scanResult the scan result to be checked. * @return a list of security params. If no known security params, return an empty list. */ public static @NonNull List generateSecurityParamsListFromScanResult( ScanResult scanResult) { List list = new ArrayList<>(); // Open network & its upgradable types if (ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_OPEN)); list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_OWE)); return list; } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_OWE)); return list; } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_OPEN)); return list; } // WEP network which has no upgradable type if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_WEP)); return list; } // WAPI PSK network which has no upgradable type if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_WAPI_PSK)); return list; } // WAPI CERT network which has no upgradable type if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_WAPI_CERT)); return list; } // WPA2 personal network & its upgradable types if (ScanResultUtil.isScanResultForPskNetwork(scanResult) && ScanResultUtil.isScanResultForSaeNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_PSK)); list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_SAE)); return list; } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_PSK)); return list; } else if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_SAE)); return list; } // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT)); } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_EAP)); list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)); } else if (ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE)); } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_EAP)); } // An Enterprise network might be a Passpoint network as well. // R3 network might be also a valid R1/R2 network. if (isScanResultForPasspointR1R2Network(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2)); } if (isScanResultForPasspointR3Network(scanResult)) { list.add(SecurityParams.createSecurityParamsBySecurityType( WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3)); } return list; } /** * Dump the provided scan results list to |pw|. */ public static void dumpScanResults(PrintWriter pw, List scanResults, long nowMs) { if (scanResults != null && scanResults.size() != 0) { pw.println(" BSSID Frequency RSSI Age(sec) SSID " + " Flags"); for (ScanResult r : scanResults) { long timeStampMs = r.timestamp / 1000; String age; if (timeStampMs <= 0) { age = "___?___"; } else if (nowMs < timeStampMs) { age = " 0.000"; } else if (timeStampMs < nowMs - 1000000) { age = ">1000.0"; } else { age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0); } String ssid = r.SSID == null ? "" : r.SSID; String rssiInfo = ""; if (ArrayUtils.size(r.radioChainInfos) == 1) { rssiInfo = String.format("%5d(%1d:%3d) ", r.level, r.radioChainInfos[0].id, r.radioChainInfos[0].level); } else if (ArrayUtils.size(r.radioChainInfos) == 2) { rssiInfo = String.format("%5d(%1d:%3d/%1d:%3d)", r.level, r.radioChainInfos[0].id, r.radioChainInfos[0].level, r.radioChainInfos[1].id, r.radioChainInfos[1].level); } else { rssiInfo = String.format("%9d ", r.level); } if ((r.flags & ScanResult.FLAG_PASSPOINT_NETWORK) == ScanResult.FLAG_PASSPOINT_NETWORK) { r.capabilities += "[PASSPOINT]"; } pw.printf(" %17s %9d %18s %7s %-32s %s\n", r.BSSID, r.frequency, rssiInfo, age, String.format("%1.32s", ssid), r.capabilities); } } } /** * Check if ScarResult list is valid. */ public static boolean validateScanResultList(List scanResults) { if (scanResults == null || scanResults.isEmpty()) { Log.w(TAG, "Empty or null ScanResult list"); return false; } for (ScanResult scanResult : scanResults) { if (!validate(scanResult)) { Log.w(TAG, "Invalid ScanResult: " + scanResult); return false; } } return true; } private static boolean validate(ScanResult scanResult) { return scanResult != null && scanResult.SSID != null && scanResult.capabilities != null && scanResult.BSSID != null; } }