1 /*
2  * Copyright (C) 2015 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 package com.android.server.wifi.util;
17 
18 import android.net.wifi.ScanResult;
19 import android.net.wifi.ScanResult.InformationElement;
20 import android.util.Log;
21 
22 import com.android.server.wifi.ByteBufferReader;
23 import com.android.server.wifi.hotspot2.NetworkDetail;
24 import com.android.server.wifi.hotspot2.anqp.Constants;
25 
26 import java.nio.BufferUnderflowException;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.util.ArrayList;
30 import java.util.BitSet;
31 
32 public class InformationElementUtil {
33     private static final String TAG = "InformationElementUtil";
34 
parseInformationElements(byte[] bytes)35     public static InformationElement[] parseInformationElements(byte[] bytes) {
36         if (bytes == null) {
37             return new InformationElement[0];
38         }
39         ByteBuffer data = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
40 
41         ArrayList<InformationElement> infoElements = new ArrayList<>();
42         boolean found_ssid = false;
43         while (data.remaining() > 1) {
44             int eid = data.get() & Constants.BYTE_MASK;
45             int elementLength = data.get() & Constants.BYTE_MASK;
46 
47             if (elementLength > data.remaining() || (eid == InformationElement.EID_SSID
48                     && found_ssid)) {
49                 // APs often pad the data with bytes that happen to match that of the EID_SSID
50                 // marker.  This is not due to a known issue for APs to incorrectly send the SSID
51                 // name multiple times.
52                 break;
53             }
54             if (eid == InformationElement.EID_SSID) {
55                 found_ssid = true;
56             }
57 
58             InformationElement ie = new InformationElement();
59             ie.id = eid;
60             ie.bytes = new byte[elementLength];
61             data.get(ie.bytes);
62             infoElements.add(ie);
63         }
64         return infoElements.toArray(new InformationElement[infoElements.size()]);
65     }
66 
67     /**
68      * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
69      *
70      * @param ies List of IEs to retrieve from
71      * @return {@link RoamingConsortium}
72      */
getRoamingConsortiumIE(InformationElement[] ies)73     public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
74         RoamingConsortium roamingConsortium = new RoamingConsortium();
75         if (ies != null) {
76             for (InformationElement ie : ies) {
77                 if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
78                     try {
79                         roamingConsortium.from(ie);
80                     } catch (RuntimeException e) {
81                         Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
82                     }
83                 }
84             }
85         }
86         return roamingConsortium;
87     }
88 
89     /**
90      * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
91      *
92      * @param ies List of IEs to retrieve from
93      * @return {@link Vsa}
94      */
getHS2VendorSpecificIE(InformationElement[] ies)95     public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
96         Vsa vsa = new Vsa();
97         if (ies != null) {
98             for (InformationElement ie : ies) {
99                 if (ie.id == InformationElement.EID_VSA) {
100                     try {
101                         vsa.from(ie);
102                     } catch (RuntimeException e) {
103                         Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
104                     }
105                 }
106             }
107         }
108         return vsa;
109     }
110 
111     /**
112      * Parse and retrieve the Interworking information element from the list of IEs.
113      *
114      * @param ies List of IEs to retrieve from
115      * @return {@link Interworking}
116      */
getInterworkingIE(InformationElement[] ies)117     public static Interworking getInterworkingIE(InformationElement[] ies) {
118         Interworking interworking = new Interworking();
119         if (ies != null) {
120             for (InformationElement ie : ies) {
121                 if (ie.id == InformationElement.EID_INTERWORKING) {
122                     try {
123                         interworking.from(ie);
124                     } catch (RuntimeException e) {
125                         Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
126                     }
127                 }
128             }
129         }
130         return interworking;
131     }
132 
133     public static class BssLoad {
134         public int stationCount = 0;
135         public int channelUtilization = 0;
136         public int capacity = 0;
137 
from(InformationElement ie)138         public void from(InformationElement ie) {
139             if (ie.id != InformationElement.EID_BSS_LOAD) {
140                 throw new IllegalArgumentException("Element id is not BSS_LOAD, : " + ie.id);
141             }
142             if (ie.bytes.length != 5) {
143                 throw new IllegalArgumentException("BSS Load element length is not 5: "
144                                                    + ie.bytes.length);
145             }
146             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
147             stationCount = data.getShort() & Constants.SHORT_MASK;
148             channelUtilization = data.get() & Constants.BYTE_MASK;
149             capacity = data.getShort() & Constants.SHORT_MASK;
150         }
151     }
152 
153     public static class HtOperation {
154         public int secondChannelOffset = 0;
155 
getChannelWidth()156         public int getChannelWidth() {
157             if (secondChannelOffset != 0) {
158                 return 1;
159             } else {
160                 return 0;
161             }
162         }
163 
getCenterFreq0(int primaryFrequency)164         public int getCenterFreq0(int primaryFrequency) {
165             //40 MHz
166             if (secondChannelOffset != 0) {
167                 if (secondChannelOffset == 1) {
168                     return primaryFrequency + 10;
169                 } else if (secondChannelOffset == 3) {
170                     return primaryFrequency - 10;
171                 } else {
172                     Log.e("HtOperation", "Error on secondChannelOffset: " + secondChannelOffset);
173                     return 0;
174                 }
175             } else {
176                 return 0;
177             }
178         }
179 
from(InformationElement ie)180         public void from(InformationElement ie) {
181             if (ie.id != InformationElement.EID_HT_OPERATION) {
182                 throw new IllegalArgumentException("Element id is not HT_OPERATION, : " + ie.id);
183             }
184             secondChannelOffset = ie.bytes[1] & 0x3;
185         }
186     }
187 
188     public static class VhtOperation {
189         public int channelMode = 0;
190         public int centerFreqIndex1 = 0;
191         public int centerFreqIndex2 = 0;
192 
isValid()193         public boolean isValid() {
194             return channelMode != 0;
195         }
196 
getChannelWidth()197         public int getChannelWidth() {
198             return channelMode + 1;
199         }
200 
getCenterFreq0()201         public int getCenterFreq0() {
202             //convert channel index to frequency in MHz, channel 36 is 5180MHz
203             return (centerFreqIndex1 - 36) * 5 + 5180;
204         }
205 
getCenterFreq1()206         public int getCenterFreq1() {
207             if (channelMode > 1) { //160MHz
208                 return (centerFreqIndex2 - 36) * 5 + 5180;
209             } else {
210                 return 0;
211             }
212         }
213 
from(InformationElement ie)214         public void from(InformationElement ie) {
215             if (ie.id != InformationElement.EID_VHT_OPERATION) {
216                 throw new IllegalArgumentException("Element id is not VHT_OPERATION, : " + ie.id);
217             }
218             channelMode = ie.bytes[0] & Constants.BYTE_MASK;
219             centerFreqIndex1 = ie.bytes[1] & Constants.BYTE_MASK;
220             centerFreqIndex2 = ie.bytes[2] & Constants.BYTE_MASK;
221         }
222     }
223 
224     public static class Interworking {
225         public NetworkDetail.Ant ant = null;
226         public boolean internet = false;
227         public long hessid = 0L;
228 
from(InformationElement ie)229         public void from(InformationElement ie) {
230             if (ie.id != InformationElement.EID_INTERWORKING) {
231                 throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
232             }
233             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
234             int anOptions = data.get() & Constants.BYTE_MASK;
235             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
236             internet = (anOptions & 0x10) != 0;
237             // There are only three possible lengths for the Interworking IE:
238             // Len 1: Access Network Options only
239             // Len 3: Access Network Options & Venue Info
240             // Len 7: Access Network Options & HESSID
241             // Len 9: Access Network Options, Venue Info, & HESSID
242             if (ie.bytes.length != 1
243                     && ie.bytes.length != 3
244                     && ie.bytes.length != 7
245                     && ie.bytes.length != 9) {
246                 throw new IllegalArgumentException(
247                         "Bad Interworking element length: " + ie.bytes.length);
248             }
249 
250             if (ie.bytes.length == 3 || ie.bytes.length == 9) {
251                 int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
252             }
253 
254             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
255                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
256             }
257         }
258     }
259 
260     public static class RoamingConsortium {
261         public int anqpOICount = 0;
262 
263         private long[] roamingConsortiums = null;
264 
getRoamingConsortiums()265         public long[] getRoamingConsortiums() {
266             return roamingConsortiums;
267         }
268 
from(InformationElement ie)269         public void from(InformationElement ie) {
270             if (ie.id != InformationElement.EID_ROAMING_CONSORTIUM) {
271                 throw new IllegalArgumentException("Element id is not ROAMING_CONSORTIUM, : "
272                         + ie.id);
273             }
274             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
275             anqpOICount = data.get() & Constants.BYTE_MASK;
276 
277             int oi12Length = data.get() & Constants.BYTE_MASK;
278             int oi1Length = oi12Length & Constants.NIBBLE_MASK;
279             int oi2Length = (oi12Length >>> 4) & Constants.NIBBLE_MASK;
280             int oi3Length = ie.bytes.length - 2 - oi1Length - oi2Length;
281             int oiCount = 0;
282             if (oi1Length > 0) {
283                 oiCount++;
284                 if (oi2Length > 0) {
285                     oiCount++;
286                     if (oi3Length > 0) {
287                         oiCount++;
288                     }
289                 }
290             }
291             roamingConsortiums = new long[oiCount];
292             if (oi1Length > 0 && roamingConsortiums.length > 0) {
293                 roamingConsortiums[0] =
294                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
295             }
296             if (oi2Length > 0 && roamingConsortiums.length > 1) {
297                 roamingConsortiums[1] =
298                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
299             }
300             if (oi3Length > 0 && roamingConsortiums.length > 2) {
301                 roamingConsortiums[2] =
302                         ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
303             }
304         }
305     }
306 
307     public static class Vsa {
308         private static final int ANQP_DOMID_BIT = 0x04;
309 
310         public NetworkDetail.HSRelease hsRelease = null;
311         public int anqpDomainID = 0;    // No domain ID treated the same as a 0; unique info per AP.
312 
from(InformationElement ie)313         public void from(InformationElement ie) {
314             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
315             if (ie.bytes.length >= 5 && data.getInt() == Constants.HS20_FRAME_PREFIX) {
316                 int hsConf = data.get() & Constants.BYTE_MASK;
317                 switch ((hsConf >> 4) & Constants.NIBBLE_MASK) {
318                     case 0:
319                         hsRelease = NetworkDetail.HSRelease.R1;
320                         break;
321                     case 1:
322                         hsRelease = NetworkDetail.HSRelease.R2;
323                         break;
324                     default:
325                         hsRelease = NetworkDetail.HSRelease.Unknown;
326                         break;
327                 }
328                 if ((hsConf & ANQP_DOMID_BIT) != 0) {
329                     if (ie.bytes.length < 7) {
330                         throw new IllegalArgumentException(
331                                 "HS20 indication element too short: " + ie.bytes.length);
332                     }
333                     anqpDomainID = data.getShort() & Constants.SHORT_MASK;
334                 }
335             }
336         }
337     }
338 
339     /**
340      * This IE contained a bit field indicating the capabilities being advertised by the STA.
341      * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
342      *
343      * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
344      * bit.
345      *
346      * Here is the wire format of this IE:
347      * | Element ID | Length | Capabilities |
348      *       1           1          n
349      */
350     public static class ExtendedCapabilities {
351         private static final int RTT_RESP_ENABLE_BIT = 70;
352         private static final int SSID_UTF8_BIT = 48;
353 
354         public BitSet capabilitiesBitSet;
355 
356         /**
357          * @return true if SSID should be interpreted using UTF-8 encoding
358          */
isStrictUtf8()359         public boolean isStrictUtf8() {
360             return capabilitiesBitSet.get(SSID_UTF8_BIT);
361         }
362 
363         /**
364          * @return true if 802.11 MC RTT Response is enabled
365          */
is80211McRTTResponder()366         public boolean is80211McRTTResponder() {
367             return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
368         }
369 
ExtendedCapabilities()370         public ExtendedCapabilities() {
371             capabilitiesBitSet = new BitSet();
372         }
373 
ExtendedCapabilities(ExtendedCapabilities other)374         public ExtendedCapabilities(ExtendedCapabilities other) {
375             capabilitiesBitSet = other.capabilitiesBitSet;
376         }
377 
378         /**
379          * Parse an ExtendedCapabilities from the IE containing raw bytes.
380          *
381          * @param ie The Information element data
382          */
from(InformationElement ie)383         public void from(InformationElement ie) {
384             capabilitiesBitSet = BitSet.valueOf(ie.bytes);
385         }
386     }
387 
388     /**
389      * parse beacon to build the capabilities
390      *
391      * This class is used to build the capabilities string of the scan results coming
392      * from HAL. It parses the ieee beacon's capability field, WPA and RSNE IE as per spec,
393      * and builds the ScanResult.capabilities String in a way that mirrors the values returned
394      * by wpa_supplicant.
395      */
396     public static class Capabilities {
397         private static final int CAP_ESS_BIT_OFFSET = 0;
398         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
399 
400         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
401         private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
402         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
403         private static final int OWE_VENDOR_OUI_TYPE = 0x1c9a6f50;
404         private static final short RSNE_VERSION = 0x0001;
405 
406         private static final int WPA_AKM_EAP = 0x01f25000;
407         private static final int WPA_AKM_PSK = 0x02f25000;
408 
409         private static final int RSN_AKM_EAP = 0x01ac0f00;
410         private static final int RSN_AKM_PSK = 0x02ac0f00;
411         private static final int RSN_AKM_FT_EAP = 0x03ac0f00;
412         private static final int RSN_AKM_FT_PSK = 0x04ac0f00;
413         private static final int RSN_AKM_EAP_SHA256 = 0x05ac0f00;
414         private static final int RSN_AKM_PSK_SHA256 = 0x06ac0f00;
415         private static final int RSN_AKM_SAE = 0x08ac0f00;
416         private static final int RSN_AKM_FT_SAE = 0x09ac0f00;
417         private static final int RSN_AKM_OWE = 0x12ac0f00;
418         private static final int RSN_AKM_EAP_SUITE_B_192 = 0x0cac0f00;
419 
420         private static final int WPA_CIPHER_NONE = 0x00f25000;
421         private static final int WPA_CIPHER_TKIP = 0x02f25000;
422         private static final int WPA_CIPHER_CCMP = 0x04f25000;
423 
424         private static final int RSN_CIPHER_NONE = 0x00ac0f00;
425         private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
426         private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
427         private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
428         private static final int RSN_CIPHER_GCMP_256 = 0x09ac0f00;
429 
430         public ArrayList<Integer> protocol;
431         public ArrayList<ArrayList<Integer>> keyManagement;
432         public ArrayList<ArrayList<Integer>> pairwiseCipher;
433         public ArrayList<Integer> groupCipher;
434         public boolean isESS;
435         public boolean isPrivacy;
436         public boolean isWPS;
437 
Capabilities()438         public Capabilities() {
439         }
440 
441         // RSNE format (size unit: byte)
442         //
443         // | Element ID | Length | Version | Group Data Cipher Suite |
444         //      1           1         2                 4
445         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
446         //              2                            4 * m
447         // | AKM Suite Count | AKM Suite List | RSN Capabilities |
448         //          2               4 * n               2
449         // | PMKID Count | PMKID List | Group Management Cipher Suite |
450         //        2          16 * s                 4
451         //
452         // Note: InformationElement.bytes has 'Element ID' and 'Length'
453         //       stripped off already
parseRsnElement(InformationElement ie)454         private void parseRsnElement(InformationElement ie) {
455             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
456 
457             try {
458                 // version
459                 if (buf.getShort() != RSNE_VERSION) {
460                     // incorrect version
461                     return;
462                 }
463 
464                 // found the RSNE IE, hence start building the capability string
465                 protocol.add(ScanResult.PROTOCOL_RSN);
466 
467                 // group data cipher suite
468                 groupCipher.add(parseRsnCipher(buf.getInt()));
469 
470                 // pairwise cipher suite count
471                 short cipherCount = buf.getShort();
472                 ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
473                 // pairwise cipher suite list
474                 for (int i = 0; i < cipherCount; i++) {
475                     rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
476                 }
477                 pairwiseCipher.add(rsnPairwiseCipher);
478 
479                 // AKM
480                 // AKM suite count
481                 short akmCount = buf.getShort();
482                 ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
483 
484                 for (int i = 0; i < akmCount; i++) {
485                     int akm = buf.getInt();
486                     switch (akm) {
487                         case RSN_AKM_EAP:
488                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
489                             break;
490                         case RSN_AKM_PSK:
491                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
492                             break;
493                         case RSN_AKM_FT_EAP:
494                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
495                             break;
496                         case RSN_AKM_FT_PSK:
497                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
498                             break;
499                         case RSN_AKM_EAP_SHA256:
500                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
501                             break;
502                         case RSN_AKM_PSK_SHA256:
503                             rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
504                             break;
505                         case RSN_AKM_SAE:
506                             rsnKeyManagement.add(ScanResult.KEY_MGMT_SAE);
507                             break;
508                         case RSN_AKM_FT_SAE:
509                             rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_SAE);
510                             break;
511                         case RSN_AKM_OWE:
512                             rsnKeyManagement.add(ScanResult.KEY_MGMT_OWE);
513                             break;
514                         case RSN_AKM_EAP_SUITE_B_192:
515                             rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SUITE_B_192);
516                             break;
517                         default:
518                             // do nothing
519                             break;
520                     }
521                 }
522                 // Default AKM
523                 if (rsnKeyManagement.isEmpty()) {
524                     rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
525                 }
526                 keyManagement.add(rsnKeyManagement);
527             } catch (BufferUnderflowException e) {
528                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
529             }
530         }
531 
parseWpaCipher(int cipher)532         private static int parseWpaCipher(int cipher) {
533             switch (cipher) {
534                 case WPA_CIPHER_NONE:
535                     return ScanResult.CIPHER_NONE;
536                 case WPA_CIPHER_TKIP:
537                     return ScanResult.CIPHER_TKIP;
538                 case WPA_CIPHER_CCMP:
539                     return ScanResult.CIPHER_CCMP;
540                 default:
541                     Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
542                             + Integer.toHexString(cipher));
543                     return ScanResult.CIPHER_NONE;
544             }
545         }
546 
parseRsnCipher(int cipher)547         private static int parseRsnCipher(int cipher) {
548             switch (cipher) {
549                 case RSN_CIPHER_NONE:
550                     return ScanResult.CIPHER_NONE;
551                 case RSN_CIPHER_TKIP:
552                     return ScanResult.CIPHER_TKIP;
553                 case RSN_CIPHER_CCMP:
554                     return ScanResult.CIPHER_CCMP;
555                 case RSN_CIPHER_GCMP_256:
556                     return ScanResult.CIPHER_GCMP_256;
557                 case RSN_CIPHER_NO_GROUP_ADDRESSED:
558                     return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
559                 default:
560                     Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
561                             + Integer.toHexString(cipher));
562                     return ScanResult.CIPHER_NONE;
563             }
564         }
565 
isWpsElement(InformationElement ie)566         private static boolean isWpsElement(InformationElement ie) {
567             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
568             try {
569                 // WPS OUI and type
570                 return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
571             } catch (BufferUnderflowException e) {
572                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
573                 return false;
574             }
575         }
576 
isWpaOneElement(InformationElement ie)577         private static boolean isWpaOneElement(InformationElement ie) {
578             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
579 
580             try {
581                 // WPA OUI and type
582                 return (buf.getInt() == WPA_VENDOR_OUI_TYPE_ONE);
583             } catch (BufferUnderflowException e) {
584                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
585                 return false;
586             }
587         }
588 
589         // WPA type 1 format (size unit: byte)
590         //
591         // | Element ID | Length | OUI | Type | Version |
592         //      1           1       3     1        2
593         // | Group Data Cipher Suite |
594         //             4
595         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
596         //              2                            4 * m
597         // | AKM Suite Count | AKM Suite List |
598         //          2               4 * n
599         //
600         // Note: InformationElement.bytes has 'Element ID' and 'Length'
601         //       stripped off already
602         //
parseWpaOneElement(InformationElement ie)603         private void parseWpaOneElement(InformationElement ie) {
604             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
605 
606             try {
607                 // skip WPA OUI and type parsing. isWpaOneElement() should have
608                 // been called for verification before we reach here.
609                 buf.getInt();
610 
611                 // version
612                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION) {
613                     // incorrect version
614                     return;
615                 }
616 
617                 // start building the string
618                 protocol.add(ScanResult.PROTOCOL_WPA);
619 
620                 // group data cipher suite
621                 groupCipher.add(parseWpaCipher(buf.getInt()));
622 
623                 // pairwise cipher suite count
624                 short cipherCount = buf.getShort();
625                 ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
626                 // pairwise chipher suite list
627                 for (int i = 0; i < cipherCount; i++) {
628                     wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
629                 }
630                 pairwiseCipher.add(wpaPairwiseCipher);
631 
632                 // AKM
633                 // AKM suite count
634                 short akmCount = buf.getShort();
635                 ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
636 
637                 // AKM suite list
638                 for (int i = 0; i < akmCount; i++) {
639                     int akm = buf.getInt();
640                     switch (akm) {
641                         case WPA_AKM_EAP:
642                             wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
643                             break;
644                         case WPA_AKM_PSK:
645                             wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
646                             break;
647                         default:
648                             // do nothing
649                             break;
650                     }
651                 }
652                 // Default AKM
653                 if (wpaKeyManagement.isEmpty()) {
654                     wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
655                 }
656                 keyManagement.add(wpaKeyManagement);
657             } catch (BufferUnderflowException e) {
658                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
659             }
660         }
661 
662         /**
663          * Parse the Information Element and the 16-bit Capability Information field
664          * to build the InformationElemmentUtil.capabilities object.
665          *
666          * @param ies            -- Information Element array
667          * @param beaconCap      -- 16-bit Beacon Capability Information field
668          * @param isOweSupported -- Boolean flag to indicate if OWE is supported by the device
669          */
670 
from(InformationElement[] ies, BitSet beaconCap, boolean isOweSupported)671         public void from(InformationElement[] ies, BitSet beaconCap, boolean isOweSupported) {
672             protocol = new ArrayList<Integer>();
673             keyManagement = new ArrayList<ArrayList<Integer>>();
674             groupCipher = new ArrayList<Integer>();
675             pairwiseCipher = new ArrayList<ArrayList<Integer>>();
676 
677             if (ies == null || beaconCap == null) {
678                 return;
679             }
680             isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
681             isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
682             for (InformationElement ie : ies) {
683                 if (ie.id == InformationElement.EID_RSN) {
684                     parseRsnElement(ie);
685                 }
686 
687                 if (ie.id == InformationElement.EID_VSA) {
688                     if (isWpaOneElement(ie)) {
689                         parseWpaOneElement(ie);
690                     }
691                     if (isWpsElement(ie)) {
692                         // TODO(b/62134557): parse WPS IE to provide finer granularity information.
693                         isWPS = true;
694                     }
695                     if (isOweSupported && isOweElement(ie)) {
696                         /* From RFC 8110: Once the client and AP have finished 802.11 association,
697                            they then complete the Diffie-Hellman key exchange and create a Pairwise
698                            Master Key (PMK) and its associated identifier, PMKID [IEEE802.11].
699                            Upon completion of 802.11 association, the AP initiates the 4-way
700                            handshake to the client using the PMK generated above.  The 4-way
701                            handshake generates a Key-Encrypting Key (KEK), a Key-Confirmation
702                            Key (KCK), and a Message Integrity Code (MIC) to use for protection
703                            of the frames that define the 4-way handshake.
704 
705                            We check if OWE is supported here because we are adding the OWE
706                            capabilities to the Open BSS. Non-supporting devices need to see this
707                            open network and ignore this element. Supporting devices need to hide
708                            the Open BSS of OWE in transition mode and connect to the Hidden one.
709                         */
710                         protocol.add(ScanResult.PROTOCOL_RSN);
711                         groupCipher.add(ScanResult.CIPHER_CCMP);
712                         ArrayList<Integer> owePairwiseCipher = new ArrayList<>();
713                         owePairwiseCipher.add(ScanResult.CIPHER_CCMP);
714                         pairwiseCipher.add(owePairwiseCipher);
715                         ArrayList<Integer> oweKeyManagement = new ArrayList<>();
716                         oweKeyManagement.add(ScanResult.KEY_MGMT_OWE_TRANSITION);
717                         keyManagement.add(oweKeyManagement);
718                     }
719                 }
720             }
721         }
722 
isOweElement(InformationElement ie)723         private static boolean isOweElement(InformationElement ie) {
724             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
725             try {
726                 // OWE OUI and type
727                 return (buf.getInt() == OWE_VENDOR_OUI_TYPE);
728             } catch (BufferUnderflowException e) {
729                 Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
730                 return false;
731             }
732         }
733 
protocolToString(int protocol)734         private String protocolToString(int protocol) {
735             switch (protocol) {
736                 case ScanResult.PROTOCOL_NONE:
737                     return "None";
738                 case ScanResult.PROTOCOL_WPA:
739                     return "WPA";
740                 case ScanResult.PROTOCOL_RSN:
741                     return "RSN";
742                 default:
743                     return "?";
744             }
745         }
746 
keyManagementToString(int akm)747         private String keyManagementToString(int akm) {
748             switch (akm) {
749                 case ScanResult.KEY_MGMT_NONE:
750                     return "None";
751                 case ScanResult.KEY_MGMT_PSK:
752                     return "PSK";
753                 case ScanResult.KEY_MGMT_EAP:
754                     return "EAP";
755                 case ScanResult.KEY_MGMT_FT_EAP:
756                     return "FT/EAP";
757                 case ScanResult.KEY_MGMT_FT_PSK:
758                     return "FT/PSK";
759                 case ScanResult.KEY_MGMT_EAP_SHA256:
760                     return "EAP-SHA256";
761                 case ScanResult.KEY_MGMT_PSK_SHA256:
762                     return "PSK-SHA256";
763                 case ScanResult.KEY_MGMT_OWE:
764                     return "OWE";
765                 case ScanResult.KEY_MGMT_OWE_TRANSITION:
766                     return "OWE_TRANSITION";
767                 case ScanResult.KEY_MGMT_SAE:
768                     return "SAE";
769                 case ScanResult.KEY_MGMT_FT_SAE:
770                     return "FT/SAE";
771                 case ScanResult.KEY_MGMT_EAP_SUITE_B_192:
772                     return "EAP_SUITE_B_192";
773                 default:
774                     return "?";
775             }
776         }
777 
cipherToString(int cipher)778         private String cipherToString(int cipher) {
779             switch (cipher) {
780                 case ScanResult.CIPHER_NONE:
781                     return "None";
782                 case ScanResult.CIPHER_CCMP:
783                     return "CCMP";
784                 case ScanResult.CIPHER_GCMP_256:
785                     return "GCMP-256";
786                 case ScanResult.CIPHER_TKIP:
787                     return "TKIP";
788                 default:
789                     return "?";
790             }
791         }
792 
793         /**
794          * Build the ScanResult.capabilities String.
795          *
796          * @return security string that mirrors what wpa_supplicant generates
797          */
generateCapabilitiesString()798         public String generateCapabilitiesString() {
799             StringBuilder capabilities = new StringBuilder();
800             // private Beacon without an RSNE or WPA IE, hence WEP0
801             boolean isWEP = (protocol.isEmpty()) && isPrivacy;
802 
803             if (isWEP) {
804                 capabilities.append("[WEP]");
805             }
806             for (int i = 0; i < protocol.size(); i++) {
807                 String capability = generateCapabilitiesStringPerProtocol(i);
808                 // add duplicate capabilities for WPA2 for backward compatibility:
809                 // duplicate "RSN" entries as "WPA2"
810                 String capWpa2 = generateWPA2CapabilitiesString(capability, i);
811                 capabilities.append(capWpa2);
812                 capabilities.append(capability);
813             }
814             if (isESS) {
815                 capabilities.append("[ESS]");
816             }
817             if (isWPS) {
818                 capabilities.append("[WPS]");
819             }
820 
821             return capabilities.toString();
822         }
823 
824         /**
825          * Build the Capability String for one protocol
826          * @param index: index number of the protocol
827          * @return security string for one protocol
828          */
generateCapabilitiesStringPerProtocol(int index)829         private String generateCapabilitiesStringPerProtocol(int index) {
830             StringBuilder capability = new StringBuilder();
831             capability.append("[").append(protocolToString(protocol.get(index)));
832 
833             if (index < keyManagement.size()) {
834                 for (int j = 0; j < keyManagement.get(index).size(); j++) {
835                     capability.append((j == 0) ? "-" : "+").append(
836                             keyManagementToString(keyManagement.get(index).get(j)));
837                 }
838             }
839             if (index < pairwiseCipher.size()) {
840                 for (int j = 0; j < pairwiseCipher.get(index).size(); j++) {
841                     capability.append((j == 0) ? "-" : "+").append(
842                             cipherToString(pairwiseCipher.get(index).get(j)));
843                 }
844             }
845             capability.append("]");
846             return capability.toString();
847         }
848 
849         /**
850          * Build the duplicate Capability String for WPA2
851          * @param cap: original capability String
852          * @param index: index number of the protocol
853          * @return security string for WPA2, empty String if protocol is not WPA2
854          */
generateWPA2CapabilitiesString(String cap, int index)855         private String generateWPA2CapabilitiesString(String cap, int index) {
856             StringBuilder capWpa2 = new StringBuilder();
857             // if not WPA2, return empty String
858             if (cap.contains("EAP_SUITE_B_192")
859                     || (!cap.contains("RSN-EAP") && !cap.contains("RSN-FT/EAP")
860                     && !cap.contains("RSN-PSK") && !cap.contains("RSN-FT/PSK"))) {
861                 return "";
862             }
863             capWpa2.append("[").append("WPA2");
864             if (index < keyManagement.size()) {
865                 for (int j = 0; j < keyManagement.get(index).size(); j++) {
866                     capWpa2.append((j == 0) ? "-" : "+").append(
867                             keyManagementToString(keyManagement.get(index).get(j)));
868                     // WPA3/WPA2 transition mode
869                     if (cap.contains("SAE")) {
870                         break;
871                     }
872                 }
873             }
874             if (index < pairwiseCipher.size()) {
875                 for (int j = 0; j < pairwiseCipher.get(index).size(); j++) {
876                     capWpa2.append((j == 0) ? "-" : "+").append(
877                             cipherToString(pairwiseCipher.get(index).get(j)));
878                 }
879             }
880             capWpa2.append("]");
881             return capWpa2.toString();
882         }
883     }
884 
885 
886     /**
887      * Parser for the Traffic Indication Map (TIM) Information Element (EID 5). This element will
888      * only be present in scan results that are derived from a Beacon Frame, not from the more
889      * plentiful probe responses. Call 'isValid()' after parsing, to ensure the results are correct.
890      */
891     public static class TrafficIndicationMap {
892         private static final int MAX_TIM_LENGTH = 254;
893         private boolean mValid = false;
894         public int mLength = 0;
895         public int mDtimCount = -1;
896         //Negative DTIM Period means no TIM element was given this frame.
897         public int mDtimPeriod = -1;
898         public int mBitmapControl = 0;
899 
900         /**
901          * Is this a valid TIM information element.
902          */
isValid()903         public boolean isValid() {
904             return mValid;
905         }
906 
907         // Traffic Indication Map format (size unit: byte)
908         //
909         //| ElementID | Length | DTIM Count | DTIM Period | BitmapControl | Partial Virtual Bitmap |
910         //      1          1          1            1               1                1 - 251
911         //
912         // Note: InformationElement.bytes has 'Element ID' and 'Length'
913         //       stripped off already
914         //
from(InformationElement ie)915         public void from(InformationElement ie) {
916             mValid = false;
917             if (ie == null || ie.bytes == null) return;
918             mLength = ie.bytes.length;
919             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
920             try {
921                 mDtimCount = data.get() & Constants.BYTE_MASK;
922                 mDtimPeriod = data.get() & Constants.BYTE_MASK;
923                 mBitmapControl = data.get() & Constants.BYTE_MASK;
924                 //A valid TIM element must have atleast one more byte
925                 data.get();
926             } catch (BufferUnderflowException e) {
927                 return;
928             }
929             if (mLength <= MAX_TIM_LENGTH && mDtimPeriod > 0) {
930                 mValid = true;
931             }
932         }
933     }
934 
935     /**
936      * This util class determines the 802.11 standard (a/b/g/n/ac) being used
937      */
938     public static class WifiMode {
939         public static final int MODE_UNDEFINED = 0; // Unknown/undefined
940         public static final int MODE_11A = 1;       // 802.11a
941         public static final int MODE_11B = 2;       // 802.11b
942         public static final int MODE_11G = 3;       // 802.11g
943         public static final int MODE_11N = 4;       // 802.11n
944         public static final int MODE_11AC = 5;      // 802.11ac
945         //<TODO> add support for 802.11ad and be more selective instead of defaulting to 11A
946 
947         /**
948          * Use frequency, max supported rate, and the existence of VHT, HT & ERP fields in scan
949          * scan result to determine the 802.11 Wifi standard being used.
950          */
determineMode(int frequency, int maxRate, boolean foundVht, boolean foundHt, boolean foundErp)951         public static int determineMode(int frequency, int maxRate, boolean foundVht,
952                 boolean foundHt, boolean foundErp) {
953             if (foundVht) {
954                 return MODE_11AC;
955             } else if (foundHt) {
956                 return MODE_11N;
957             } else if (foundErp) {
958                 return MODE_11G;
959             } else if (frequency < 3000) {
960                 if (maxRate < 24000000) {
961                     return MODE_11B;
962                 } else {
963                     return MODE_11G;
964                 }
965             } else {
966                 return MODE_11A;
967             }
968         }
969 
970         /**
971          * Map the wifiMode integer to its type, and output as String MODE_11<A/B/G/N/AC>
972          */
toString(int mode)973         public static String toString(int mode) {
974             switch(mode) {
975                 case MODE_11A:
976                     return "MODE_11A";
977                 case MODE_11B:
978                     return "MODE_11B";
979                 case MODE_11G:
980                     return "MODE_11G";
981                 case MODE_11N:
982                     return "MODE_11N";
983                 case MODE_11AC:
984                     return "MODE_11AC";
985                 default:
986                     return "MODE_UNDEFINED";
987             }
988         }
989     }
990 
991     /**
992      * Parser for both the Supported Rates & Extended Supported Rates Information Elements
993      */
994     public static class SupportedRates {
995         public static final int MASK = 0x7F; // 0111 1111
996         public boolean mValid = false;
997         public ArrayList<Integer> mRates;
998 
SupportedRates()999         public SupportedRates() {
1000             mRates = new ArrayList<Integer>();
1001         }
1002 
1003         /**
1004          * Is this a valid Supported Rates information element.
1005          */
isValid()1006         public boolean isValid() {
1007             return mValid;
1008         }
1009 
1010         /**
1011          * get the Rate in bits/s from associated byteval
1012          */
getRateFromByte(int byteVal)1013         public static int getRateFromByte(int byteVal) {
1014             byteVal &= MASK;
1015             switch(byteVal) {
1016                 case 2:
1017                     return 1000000;
1018                 case 4:
1019                     return 2000000;
1020                 case 11:
1021                     return 5500000;
1022                 case 12:
1023                     return 6000000;
1024                 case 18:
1025                     return 9000000;
1026                 case 22:
1027                     return 11000000;
1028                 case 24:
1029                     return 12000000;
1030                 case 36:
1031                     return 18000000;
1032                 case 44:
1033                     return 22000000;
1034                 case 48:
1035                     return 24000000;
1036                 case 66:
1037                     return 33000000;
1038                 case 72:
1039                     return 36000000;
1040                 case 96:
1041                     return 48000000;
1042                 case 108:
1043                     return 54000000;
1044                 default:
1045                     //ERROR UNKNOWN RATE
1046                     return -1;
1047             }
1048         }
1049 
1050         // Supported Rates format (size unit: byte)
1051         //
1052         //| ElementID | Length | Supported Rates  [7 Little Endian Info bits - 1 Flag bit]
1053         //      1          1          1 - 8
1054         //
1055         // Note: InformationElement.bytes has 'Element ID' and 'Length'
1056         //       stripped off already
1057         //
from(InformationElement ie)1058         public void from(InformationElement ie) {
1059             mValid = false;
1060             if (ie == null || ie.bytes == null || ie.bytes.length > 8 || ie.bytes.length < 1)  {
1061                 return;
1062             }
1063             ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
1064             try {
1065                 for (int i = 0; i < ie.bytes.length; i++) {
1066                     int rate = getRateFromByte(data.get());
1067                     if (rate > 0) {
1068                         mRates.add(rate);
1069                     } else {
1070                         return;
1071                     }
1072                 }
1073             } catch (BufferUnderflowException e) {
1074                 return;
1075             }
1076             mValid = true;
1077             return;
1078         }
1079 
1080         /**
1081          * Lists the rates in a human readable string
1082          */
toString()1083         public String toString() {
1084             StringBuilder sbuf = new StringBuilder();
1085             for (Integer rate : mRates) {
1086                 sbuf.append(String.format("%.1f", (double) rate / 1000000) + ", ");
1087             }
1088             return sbuf.toString();
1089         }
1090     }
1091 }
1092