blob: b7e26aec1c546b00d5b794a2454dda12f99996e1 [file] [log] [blame]
/*
* 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<SecurityParams> 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<SecurityParams> generateSecurityParamsListFromScanResult(
ScanResult scanResult) {
List<SecurityParams> 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<ScanResult> 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<ScanResult> 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;
}
}