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