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