• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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