1 /* 2 * Copyright (C) 2017 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.Nullable; 20 import android.net.MacAddress; 21 import android.net.wifi.WifiConfiguration; 22 import android.net.wifi.util.HexEncoding; 23 import android.text.TextUtils; 24 25 import com.android.server.wifi.ByteBufferReader; 26 import com.android.server.wifi.SupplicantStaIfaceHal.StaIfaceReasonCode; 27 import com.android.server.wifi.WifiConfigurationUtil; 28 import com.android.server.wifi.WifiGlobals; 29 30 import java.nio.BufferUnderflowException; 31 import java.nio.ByteBuffer; 32 import java.nio.ByteOrder; 33 import java.nio.CharBuffer; 34 import java.nio.charset.CharacterCodingException; 35 import java.nio.charset.CharsetDecoder; 36 import java.nio.charset.CharsetEncoder; 37 import java.nio.charset.StandardCharsets; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.BitSet; 41 42 /** 43 * Provide utility functions for native interfacing modules. 44 */ 45 public class NativeUtil { 46 private static final String ANY_MAC_STR = "any"; 47 public static final byte[] ANY_MAC_BYTES = {0, 0, 0, 0, 0, 0}; 48 private static final int MAC_LENGTH = 6; 49 private static final int MAC_OUI_LENGTH = 3; 50 private static final int MAC_STR_LENGTH = MAC_LENGTH * 2 + 5; 51 private static final int SSID_BYTES_MAX_LEN = 32; 52 53 /** 54 * Convert the string to byte array list. 55 * 56 * @return the UTF_8 char byte values of str, as an ArrayList. 57 * @throws IllegalArgumentException if a null or unencodable string is sent. 58 */ stringToByteArrayList(String str)59 public static ArrayList<Byte> stringToByteArrayList(String str) { 60 if (str == null) { 61 throw new IllegalArgumentException("null string"); 62 } 63 // Ensure that the provided string is UTF_8 encoded. 64 CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); 65 try { 66 ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str)); 67 byte[] byteArray = new byte[encoded.remaining()]; 68 encoded.get(byteArray); 69 return byteArrayToArrayList(byteArray); 70 } catch (CharacterCodingException cce) { 71 throw new IllegalArgumentException("cannot be utf-8 encoded", cce); 72 } 73 } 74 75 /** 76 * Convert the byte array list to string. 77 * 78 * @return the string decoded from UTF_8 byte values in byteArrayList. 79 * @throws IllegalArgumentException if a null byte array list is sent. 80 */ stringFromByteArrayList(ArrayList<Byte> byteArrayList)81 public static String stringFromByteArrayList(ArrayList<Byte> byteArrayList) { 82 if (byteArrayList == null) { 83 throw new IllegalArgumentException("null byte array list"); 84 } 85 byte[] byteArray = new byte[byteArrayList.size()]; 86 int i = 0; 87 for (Byte b : byteArrayList) { 88 byteArray[i] = b; 89 i++; 90 } 91 return new String(byteArray, StandardCharsets.UTF_8); 92 } 93 94 /** 95 * Convert the string to byte array. 96 * 97 * @return the UTF_8 char byte values of str, as an Array. 98 * @throws IllegalArgumentException if a null string is sent. 99 */ stringToByteArray(String str)100 public static byte[] stringToByteArray(String str) { 101 if (str == null) { 102 throw new IllegalArgumentException("null string"); 103 } 104 return str.getBytes(StandardCharsets.UTF_8); 105 } 106 107 /** 108 * Convert the byte array list to string. 109 * 110 * @return the string decoded from UTF_8 byte values in byteArray. 111 * @throws IllegalArgumentException if a null byte array is sent. 112 */ stringFromByteArray(byte[] byteArray)113 public static String stringFromByteArray(byte[] byteArray) { 114 if (byteArray == null) { 115 throw new IllegalArgumentException("null byte array"); 116 } 117 return new String(byteArray); 118 } 119 120 /** 121 * Converts a mac address string to an array of Bytes. 122 * 123 * @param macStr string of format: "XX:XX:XX:XX:XX:XX" or "XXXXXXXXXXXX", where X is any 124 * hexadecimal digit. 125 * Passing null, empty string or "any" is the same as 00:00:00:00:00:00 126 * @throws IllegalArgumentException for various malformed inputs. 127 */ macAddressToByteArray(String macStr)128 public static byte[] macAddressToByteArray(String macStr) { 129 if (TextUtils.isEmpty(macStr) || ANY_MAC_STR.equals(macStr)) return ANY_MAC_BYTES; 130 String cleanMac = macStr.replace(":", ""); 131 if (cleanMac.length() != MAC_LENGTH * 2) { 132 throw new IllegalArgumentException("invalid mac string length: " + cleanMac); 133 } 134 return HexEncoding.decode(cleanMac.toCharArray(), false); 135 } 136 137 /** 138 * Converts a MAC address from the given string representation to android.net.MacAddress. A 139 * valid String representation for a MacAddress is a series of 6 values in the range [0,ff] 140 * printed in hexadecimal and joined by ':' characters. 141 * 142 * @param macAddress a String representation of a MAC address. 143 * @return the MacAddress corresponding to the given string representation or null. 144 */ getMacAddressOrNull(@ullable String macAddress)145 public static MacAddress getMacAddressOrNull(@Nullable String macAddress) { 146 if (macAddress == null) return null; 147 try { 148 return MacAddress.fromString(macAddress); 149 } catch (IllegalArgumentException e) { 150 return null; 151 } 152 } 153 154 /** 155 * Converts an array of 6 bytes to a HexEncoded String with format: "XX:XX:XX:XX:XX:XX", where X 156 * is any hexadecimal digit. 157 * 158 * @param macArray byte array of mac values, must have length 6 159 * @throws IllegalArgumentException for malformed inputs. 160 */ macAddressFromByteArray(byte[] macArray)161 public static String macAddressFromByteArray(byte[] macArray) { 162 if (macArray == null) { 163 throw new IllegalArgumentException("null mac bytes"); 164 } 165 if (macArray.length != MAC_LENGTH) { 166 throw new IllegalArgumentException("invalid macArray length: " + macArray.length); 167 } 168 StringBuilder sb = new StringBuilder(MAC_STR_LENGTH); 169 for (int i = 0; i < macArray.length; i++) { 170 if (i != 0) sb.append(":"); 171 sb.append(new String(HexEncoding.encode(macArray, i, 1))); 172 } 173 return sb.toString().toLowerCase(); 174 } 175 176 /** 177 * Converts a mac address OUI string to an array of Bytes. 178 * 179 * @param macStr string of format: "XX:XX:XX" or "XXXXXX", where X is any hexadecimal digit. 180 * @throws IllegalArgumentException for various malformed inputs. 181 */ macAddressOuiToByteArray(String macStr)182 public static byte[] macAddressOuiToByteArray(String macStr) { 183 if (macStr == null) { 184 throw new IllegalArgumentException("null mac string"); 185 } 186 String cleanMac = macStr.replace(":", ""); 187 if (cleanMac.length() != MAC_OUI_LENGTH * 2) { 188 throw new IllegalArgumentException("invalid mac oui string length: " + cleanMac); 189 } 190 return HexEncoding.decode(cleanMac.toCharArray(), false); 191 } 192 193 /** 194 * Converts an array of 6 bytes to a long representing the MAC address. 195 * 196 * @param macArray byte array of mac values, must have length 6 197 * @return Long value of the mac address. 198 * @throws IllegalArgumentException for malformed inputs. 199 */ macAddressToLong(byte[] macArray)200 public static Long macAddressToLong(byte[] macArray) { 201 if (macArray == null) { 202 throw new IllegalArgumentException("null mac bytes"); 203 } 204 if (macArray.length != MAC_LENGTH) { 205 throw new IllegalArgumentException("invalid macArray length: " + macArray.length); 206 } 207 try { 208 return ByteBufferReader.readInteger( 209 ByteBuffer.wrap(macArray), ByteOrder.BIG_ENDIAN, macArray.length); 210 } catch (BufferUnderflowException | IllegalArgumentException e) { 211 throw new IllegalArgumentException("invalid macArray"); 212 } 213 } 214 215 /** 216 * Remove enclosing quotes from the provided string. 217 * 218 * @param quotedStr String to be unquoted. 219 * @return String without the enclosing quotes. 220 */ removeEnclosingQuotes(String quotedStr)221 public static String removeEnclosingQuotes(String quotedStr) { 222 int length = quotedStr.length(); 223 if ((length >= 2) 224 && (quotedStr.charAt(0) == '"') && (quotedStr.charAt(length - 1) == '"')) { 225 return quotedStr.substring(1, length - 1); 226 } 227 return quotedStr; 228 } 229 230 /** 231 * Add enclosing quotes to the provided string. 232 * 233 * @param str String to be quoted. 234 * @return String with the enclosing quotes. 235 */ addEnclosingQuotes(String str)236 public static String addEnclosingQuotes(String str) { 237 return "\"" + str + "\""; 238 } 239 240 /** 241 * Converts an string to an arraylist of UTF_8 byte values. 242 * These forms are acceptable: 243 * a) UTF-8 String encapsulated in quotes, or 244 * b) Hex string with no delimiters. 245 * 246 * @param str String to be converted. 247 * @throws IllegalArgumentException for null string. 248 */ hexOrQuotedStringToBytes(String str)249 public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) { 250 if (str == null) { 251 throw new IllegalArgumentException("null string"); 252 } 253 int length = str.length(); 254 if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) { 255 str = str.substring(1, str.length() - 1); 256 return stringToByteArrayList(str); 257 } else { 258 return byteArrayToArrayList(hexStringToByteArray(str)); 259 } 260 } 261 262 /** 263 * Converts an ArrayList<Byte> of UTF_8 byte values to string. 264 * The string will either be: 265 * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null), 266 * or 267 * b) Hex string with no delimiters. 268 * 269 * @param bytes List of bytes for ssid. 270 * @throws IllegalArgumentException for null bytes. 271 */ bytesToHexOrQuotedString(ArrayList<Byte> bytes)272 public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) { 273 if (bytes == null) { 274 throw new IllegalArgumentException("null ssid bytes"); 275 } 276 byte[] byteArray = byteArrayFromArrayList(bytes); 277 // Check for 0's in the byte stream in which case we cannot convert this into a string. 278 if (!bytes.contains(Byte.valueOf((byte) 0))) { 279 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 280 try { 281 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray)); 282 return "\"" + decoded.toString() + "\""; 283 } catch (CharacterCodingException cce) { 284 } 285 } 286 return hexStringFromByteArray(byteArray); 287 } 288 289 /** 290 * Converts an ssid string to an arraylist of UTF_8 byte values. 291 * These forms are acceptable: 292 * a) UTF-8 String encapsulated in quotes, or 293 * b) Hex string with no delimiters. 294 * 295 * @param ssidStr String to be converted. 296 * @throws IllegalArgumentException for null string. 297 */ decodeSsid(String ssidStr)298 public static ArrayList<Byte> decodeSsid(String ssidStr) { 299 ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr); 300 if (ssidBytes.size() > SSID_BYTES_MAX_LEN) { 301 throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size()); 302 } 303 return ssidBytes; 304 } 305 306 /** 307 * Converts an ArrayList<Byte> of UTF_8 byte values to ssid string. 308 * The string will either be: 309 * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null), 310 * or 311 * b) Hex string with no delimiters. 312 * 313 * @param ssidBytes List of bytes for ssid. 314 * @throws IllegalArgumentException for null bytes. 315 */ encodeSsid(ArrayList<Byte> ssidBytes)316 public static String encodeSsid(ArrayList<Byte> ssidBytes) { 317 if (ssidBytes.size() > SSID_BYTES_MAX_LEN) { 318 throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size()); 319 } 320 return bytesToHexOrQuotedString(ssidBytes); 321 } 322 323 /** 324 * Convert from an array of primitive bytes to an array list of Byte. 325 */ byteArrayToArrayList(byte[] bytes)326 public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) { 327 ArrayList<Byte> byteList = new ArrayList<>(); 328 for (Byte b : bytes) { 329 byteList.add(b); 330 } 331 return byteList; 332 } 333 334 /** 335 * Convert from an array list of Byte to an array of primitive bytes. 336 */ byteArrayFromArrayList(ArrayList<Byte> bytes)337 public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) { 338 byte[] byteArray = new byte[bytes.size()]; 339 int i = 0; 340 for (Byte b : bytes) { 341 byteArray[i++] = b; 342 } 343 return byteArray; 344 } 345 346 /** 347 * Converts a hex string to byte array. 348 * 349 * @param hexStr String to be converted. 350 * @throws IllegalArgumentException for null string. 351 */ hexStringToByteArray(String hexStr)352 public static byte[] hexStringToByteArray(String hexStr) { 353 if (hexStr == null) { 354 throw new IllegalArgumentException("null hex string"); 355 } 356 return HexEncoding.decode(hexStr.toCharArray(), false); 357 } 358 359 /** 360 * Converts a byte array to hex string. 361 * 362 * @param bytes List of bytes for ssid. 363 * @throws IllegalArgumentException for null bytes. 364 */ hexStringFromByteArray(byte[] bytes)365 public static String hexStringFromByteArray(byte[] bytes) { 366 if (bytes == null) { 367 throw new IllegalArgumentException("null hex bytes"); 368 } 369 return new String(HexEncoding.encode(bytes)).toLowerCase(); 370 } 371 372 /** 373 * Converts an 8 byte array to a WPS device type string 374 * { 0, 1, 2, -1, 4, 5, 6, 7 } --> "1-02FF0405-1543"; 375 */ wpsDevTypeStringFromByteArray(byte[] devType)376 public static String wpsDevTypeStringFromByteArray(byte[] devType) { 377 byte[] a = devType; 378 int x = ((a[0] & 0xFF) << 8) | (a[1] & 0xFF); 379 String y = new String(HexEncoding.encode(Arrays.copyOfRange(devType, 2, 6))); 380 int z = ((a[6] & 0xFF) << 8) | (a[7] & 0xFF); 381 return String.format("%d-%s-%d", x, y, z); 382 } 383 384 /** 385 * Update PMF requirement if auto-upgrade offload is supported. 386 * 387 * If SAE auto-upgrade offload is supported and this config enables 388 * both PSK and SAE, do not set PMF requirement to 389 * mandatory to allow the device to roam between PSK and SAE BSSes. 390 * wpa_supplicant will set PMF requirement to optional by default. 391 */ getOptimalPmfSettingForConfig(WifiConfiguration config, boolean isPmfRequiredFromSelectedSecurityParams, WifiGlobals wifiGlobals)392 public static boolean getOptimalPmfSettingForConfig(WifiConfiguration config, 393 boolean isPmfRequiredFromSelectedSecurityParams, WifiGlobals wifiGlobals) { 394 if (isPskSaeParamsMergeable(config, wifiGlobals)) { 395 return false; 396 } 397 return isPmfRequiredFromSelectedSecurityParams; 398 } 399 400 /** 401 * Update group ciphers if auto-upgrade offload is supported. 402 * 403 * If auto-upgrade offload is supported and this config enables both PSK and 404 * SAE, merge allowed group ciphers to allow native service to roam 405 * between two types. 406 */ getOptimalGroupCiphersForConfig(WifiConfiguration config, BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobals)407 public static BitSet getOptimalGroupCiphersForConfig(WifiConfiguration config, 408 BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobals) { 409 BitSet ciphers = ciphersFromSelectedParams; 410 if (isPskSaeParamsMergeable(config, wifiGlobals)) { 411 ciphers = (BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK) 412 .getAllowedGroupCiphers().clone(); 413 ciphers.or((BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE) 414 .getAllowedGroupCiphers().clone()); 415 } 416 return ciphers; 417 } 418 419 /** 420 * Update pairwise ciphers if auto-upgrade offload is supported. 421 * 422 * If auto-upgrade offload is supported and this config enables both PSK and 423 * SAE, merge allowed pairwise ciphers to allow native service to roam 424 * between two types. 425 */ getOptimalPairwiseCiphersForConfig(WifiConfiguration config, BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobal)426 public static BitSet getOptimalPairwiseCiphersForConfig(WifiConfiguration config, 427 BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobal) { 428 BitSet ciphers = ciphersFromSelectedParams; 429 if (isPskSaeParamsMergeable(config, wifiGlobal)) { 430 ciphers = (BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK) 431 .getAllowedPairwiseCiphers().clone(); 432 ciphers.or((BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE) 433 .getAllowedPairwiseCiphers().clone()); 434 } 435 return ciphers; 436 } 437 438 /** 439 * Check if the EAPOL 4-WAY H/S failure is due to wrong password 440 * 441 */ isEapol4WayHandshakeFailureDueToWrongPassword(WifiConfiguration config, boolean locallyGenerated, int reasonCode)442 public static boolean isEapol4WayHandshakeFailureDueToWrongPassword(WifiConfiguration config, 443 boolean locallyGenerated, int reasonCode) { 444 if (!(WifiConfigurationUtil.isConfigForPskNetwork(config) 445 || WifiConfigurationUtil.isConfigForWapiPskNetwork( 446 config))) { 447 return false; 448 } 449 // Filter out the disconnect triggered by the supplicant due to WPA/RSN IE mismatch in the 450 // received EAPOL message 3/4 with the Beacon/ProbeResp WPA/RSN IE. 451 if (locallyGenerated && reasonCode == StaIfaceReasonCode.IE_IN_4WAY_DIFFERS) { 452 return false; 453 } 454 // Some APs send de-authentication/disassociation with reason code 5 455 // (NO_MORE_STAS - Disassociated because AP is unable to handle all currently associated 456 // STAs) in the middle of EAPOL H/S. Filter out this reason code. 457 if (!locallyGenerated && reasonCode == StaIfaceReasonCode.DISASSOC_AP_BUSY) { 458 return false; 459 } 460 return true; 461 } 462 isPskSaeParamsMergeable( WifiConfiguration config, WifiGlobals wifiGlobals)463 private static boolean isPskSaeParamsMergeable( 464 WifiConfiguration config, WifiGlobals wifiGlobals) { 465 if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK) 466 && config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK).isEnabled() 467 && config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) 468 && config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE).isEnabled() 469 && wifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) { 470 return true; 471 } 472 return false; 473 } 474 475 } 476