1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.util; 18 19 import android.annotation.NonNull; 20 import android.net.wifi.WifiConfiguration; 21 import android.net.wifi.WifiEnterpriseConfig; 22 import android.telephony.ImsiEncryptionInfo; 23 import android.telephony.SubscriptionManager; 24 import android.telephony.TelephonyManager; 25 import android.util.Base64; 26 import android.util.Log; 27 import android.util.Pair; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.wifi.CarrierNetworkConfig; 31 import com.android.server.wifi.WifiNative; 32 33 import java.security.InvalidKeyException; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.PublicKey; 36 import java.util.HashMap; 37 38 import javax.annotation.Nonnull; 39 import javax.crypto.BadPaddingException; 40 import javax.crypto.Cipher; 41 import javax.crypto.IllegalBlockSizeException; 42 import javax.crypto.NoSuchPaddingException; 43 44 /** 45 * Utilities for the Wifi Service to interact with telephony. 46 * TODO(b/132188983): Refactor into TelephonyFacade which owns all instances of 47 * TelephonyManager/SubscriptionManager in Wifi 48 */ 49 public class TelephonyUtil { 50 public static final String TAG = "TelephonyUtil"; 51 public static final String DEFAULT_EAP_PREFIX = "\0"; 52 53 public static final int CARRIER_INVALID_TYPE = -1; 54 public static final int CARRIER_MNO_TYPE = 0; // Mobile Network Operator 55 public static final int CARRIER_MVNO_TYPE = 1; // Mobile Virtual Network Operator 56 public static final String ANONYMOUS_IDENTITY = "anonymous"; 57 public static final String THREE_GPP_NAI_REALM_FORMAT = "wlan.mnc%s.mcc%s.3gppnetwork.org"; 58 59 // IMSI encryption method: RSA-OAEP with SHA-256 hash function 60 private static final String IMSI_CIPHER_TRANSFORMATION = 61 "RSA/ECB/OAEPwithSHA-256andMGF1Padding"; 62 63 private static final HashMap<Integer, String> EAP_METHOD_PREFIX = new HashMap<>(); 64 static { EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0")65 EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0"); EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1")66 EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1"); EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6")67 EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6"); 68 } 69 70 /** 71 * 3GPP TS 11.11 2G_authentication command/response 72 * Input: [RAND] 73 * Output: [SRES][Cipher Key Kc] 74 */ 75 private static final int START_SRES_POS = 0; // MUST be 0 76 private static final int SRES_LEN = 4; 77 private static final int START_KC_POS = START_SRES_POS + SRES_LEN; 78 private static final int KC_LEN = 8; 79 80 /** 81 * Get the identity for the current SIM or null if the SIM is not available 82 * 83 * @param tm TelephonyManager instance 84 * @param config WifiConfiguration that indicates what sort of authentication is necessary 85 * @param telephonyUtil TelephonyUtil instance 86 * @param carrierNetworkConfig CarrierNetworkConfig instance 87 * @return Pair<identify, encrypted identity> or null if the SIM is not available 88 * or config is invalid 89 */ getSimIdentity(TelephonyManager tm, TelephonyUtil telephonyUtil, WifiConfiguration config, CarrierNetworkConfig carrierNetworkConfig)90 public static Pair<String, String> getSimIdentity(TelephonyManager tm, 91 TelephonyUtil telephonyUtil, 92 WifiConfiguration config, CarrierNetworkConfig carrierNetworkConfig) { 93 if (tm == null) { 94 Log.e(TAG, "No valid TelephonyManager"); 95 return null; 96 } 97 TelephonyManager defaultDataTm = tm.createForSubscriptionId( 98 SubscriptionManager.getDefaultDataSubscriptionId()); 99 if (carrierNetworkConfig == null) { 100 Log.e(TAG, "No valid CarrierNetworkConfig"); 101 return null; 102 } 103 String imsi = defaultDataTm.getSubscriberId(); 104 String mccMnc = ""; 105 106 if (defaultDataTm.getSimState() == TelephonyManager.SIM_STATE_READY) { 107 mccMnc = defaultDataTm.getSimOperator(); 108 } 109 110 String identity = buildIdentity(getSimMethodForConfig(config), imsi, mccMnc, false); 111 if (identity == null) { 112 Log.e(TAG, "Failed to build the identity"); 113 return null; 114 } 115 116 ImsiEncryptionInfo imsiEncryptionInfo; 117 try { 118 imsiEncryptionInfo = defaultDataTm.getCarrierInfoForImsiEncryption( 119 TelephonyManager.KEY_TYPE_WLAN); 120 } catch (RuntimeException e) { 121 Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage()); 122 return null; 123 } 124 if (imsiEncryptionInfo == null) { 125 // Does not support encrypted identity. 126 return Pair.create(identity, ""); 127 } 128 129 String encryptedIdentity = buildEncryptedIdentity(telephonyUtil, identity, 130 imsiEncryptionInfo); 131 132 // In case of failure for encryption, abort current EAP authentication. 133 if (encryptedIdentity == null) { 134 Log.e(TAG, "failed to encrypt the identity"); 135 return null; 136 } 137 return Pair.create(identity, encryptedIdentity); 138 } 139 140 /** 141 * Gets Anonymous identity for current active SIM. 142 * 143 * @param tm TelephonyManager instance 144 * @return anonymous identity@realm which is based on current MCC/MNC, {@code null} if SIM is 145 * not ready or absent. 146 */ getAnonymousIdentityWith3GppRealm(@onnull TelephonyManager tm)147 public static String getAnonymousIdentityWith3GppRealm(@Nonnull TelephonyManager tm) { 148 if (tm == null) { 149 return null; 150 } 151 TelephonyManager defaultDataTm = tm.createForSubscriptionId( 152 SubscriptionManager.getDefaultDataSubscriptionId()); 153 if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) { 154 return null; 155 } 156 String mccMnc = defaultDataTm.getSimOperator(); 157 if (mccMnc == null || mccMnc.isEmpty()) { 158 return null; 159 } 160 161 // Extract mcc & mnc from mccMnc 162 String mcc = mccMnc.substring(0, 3); 163 String mnc = mccMnc.substring(3); 164 165 if (mnc.length() == 2) { 166 mnc = "0" + mnc; 167 } 168 169 String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc); 170 return ANONYMOUS_IDENTITY + "@" + realm; 171 } 172 173 /** 174 * Encrypt the given data with the given public key. The encrypted data will be returned as 175 * a Base64 encoded string. 176 * 177 * @param key The public key to use for encryption 178 * @param encodingFlag base64 encoding flag 179 * @return Base64 encoded string, or null if encryption failed 180 */ 181 @VisibleForTesting encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag)182 public String encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag) { 183 try { 184 Cipher cipher = Cipher.getInstance(IMSI_CIPHER_TRANSFORMATION); 185 cipher.init(Cipher.ENCRYPT_MODE, key); 186 byte[] encryptedBytes = cipher.doFinal(data); 187 188 return Base64.encodeToString(encryptedBytes, 0, encryptedBytes.length, encodingFlag); 189 } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException 190 | IllegalBlockSizeException | BadPaddingException e) { 191 Log.e(TAG, "Encryption failed: " + e.getMessage()); 192 return null; 193 } 194 } 195 196 /** 197 * Create the encrypted identity. 198 * 199 * Prefix value: 200 * "0" - EAP-AKA Identity 201 * "1" - EAP-SIM Identity 202 * "6" - EAP-AKA' Identity 203 * Encrypted identity format: prefix|IMSI@<NAIRealm> 204 * @param telephonyUtil TelephonyUtil instance 205 * @param identity permanent identity with format based on section 4.1.1.6 of RFC 4187 206 * and 4.2.1.6 of RFC 4186. 207 * @param imsiEncryptionInfo The IMSI encryption info retrieved from the SIM 208 * @return "\0" + encryptedIdentity + "{, Key Identifier AVP}" 209 */ buildEncryptedIdentity(TelephonyUtil telephonyUtil, String identity, ImsiEncryptionInfo imsiEncryptionInfo)210 private static String buildEncryptedIdentity(TelephonyUtil telephonyUtil, String identity, 211 ImsiEncryptionInfo imsiEncryptionInfo) { 212 if (imsiEncryptionInfo == null) { 213 Log.e(TAG, "imsiEncryptionInfo is not valid"); 214 return null; 215 } 216 if (identity == null) { 217 Log.e(TAG, "identity is not valid"); 218 return null; 219 } 220 221 // Build and return the encrypted identity. 222 String encryptedIdentity = telephonyUtil.encryptDataUsingPublicKey( 223 imsiEncryptionInfo.getPublicKey(), identity.getBytes(), Base64.NO_WRAP); 224 if (encryptedIdentity == null) { 225 Log.e(TAG, "Failed to encrypt IMSI"); 226 return null; 227 } 228 encryptedIdentity = DEFAULT_EAP_PREFIX + encryptedIdentity; 229 if (imsiEncryptionInfo.getKeyIdentifier() != null) { 230 // Include key identifier AVP (Attribute Value Pair). 231 encryptedIdentity = encryptedIdentity + "," + imsiEncryptionInfo.getKeyIdentifier(); 232 } 233 return encryptedIdentity; 234 } 235 236 /** 237 * Create an identity used for SIM-based EAP authentication. The identity will be based on 238 * the info retrieved from the SIM card, such as IMSI and IMSI encryption info. The IMSI 239 * contained in the identity will be encrypted if IMSI encryption info is provided. 240 * 241 * See rfc4186 & rfc4187 & rfc5448: 242 * 243 * Identity format: 244 * Prefix | [IMSI || Encrypted IMSI] | @realm | {, Key Identifier AVP} 245 * where "|" denotes concatenation, "||" denotes exclusive value, "{}" 246 * denotes optional value, and realm is the 3GPP network domain name derived from the given 247 * MCC/MNC according to the 3GGP spec(TS23.003). 248 * 249 * Prefix value: 250 * "\0" - Encrypted Identity 251 * "0" - EAP-AKA Identity 252 * "1" - EAP-SIM Identity 253 * "6" - EAP-AKA' Identity 254 * 255 * Encrypted IMSI: 256 * Base64{RSA_Public_Key_Encryption{eapPrefix | IMSI}} 257 * where "|" denotes concatenation, 258 * 259 * @param eapMethod EAP authentication method: EAP-SIM, EAP-AKA, EAP-AKA' 260 * @param imsi The IMSI retrieved from the SIM 261 * @param mccMnc The MCC MNC identifier retrieved from the SIM 262 * @param isEncrypted Whether the imsi is encrypted or not. 263 * @return the eap identity, built using either the encrypted or un-encrypted IMSI. 264 */ buildIdentity(int eapMethod, String imsi, String mccMnc, boolean isEncrypted)265 private static String buildIdentity(int eapMethod, String imsi, String mccMnc, 266 boolean isEncrypted) { 267 if (imsi == null || imsi.isEmpty()) { 268 Log.e(TAG, "No IMSI or IMSI is null"); 269 return null; 270 } 271 272 String prefix = isEncrypted ? DEFAULT_EAP_PREFIX : EAP_METHOD_PREFIX.get(eapMethod); 273 if (prefix == null) { 274 return null; 275 } 276 277 /* extract mcc & mnc from mccMnc */ 278 String mcc; 279 String mnc; 280 if (mccMnc != null && !mccMnc.isEmpty()) { 281 mcc = mccMnc.substring(0, 3); 282 mnc = mccMnc.substring(3); 283 if (mnc.length() == 2) { 284 mnc = "0" + mnc; 285 } 286 } else { 287 // extract mcc & mnc from IMSI, assume mnc size is 3 288 mcc = imsi.substring(0, 3); 289 mnc = imsi.substring(3, 6); 290 } 291 292 String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc); 293 return prefix + imsi + "@" + naiRealm; 294 } 295 296 /** 297 * Return the associated SIM method for the configuration. 298 * 299 * @param config WifiConfiguration corresponding to the network. 300 * @return the outer EAP method associated with this SIM configuration. 301 */ getSimMethodForConfig(WifiConfiguration config)302 private static int getSimMethodForConfig(WifiConfiguration config) { 303 if (config == null || config.enterpriseConfig == null) { 304 return WifiEnterpriseConfig.Eap.NONE; 305 } 306 int eapMethod = config.enterpriseConfig.getEapMethod(); 307 if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) { 308 // Translate known inner eap methods into an equivalent outer eap method. 309 switch (config.enterpriseConfig.getPhase2Method()) { 310 case WifiEnterpriseConfig.Phase2.SIM: 311 eapMethod = WifiEnterpriseConfig.Eap.SIM; 312 break; 313 case WifiEnterpriseConfig.Phase2.AKA: 314 eapMethod = WifiEnterpriseConfig.Eap.AKA; 315 break; 316 case WifiEnterpriseConfig.Phase2.AKA_PRIME: 317 eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME; 318 break; 319 } 320 } 321 322 return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE; 323 } 324 325 /** 326 * Checks if the network is a SIM config. 327 * 328 * @param config Config corresponding to the network. 329 * @return true if it is a SIM config, false otherwise. 330 */ isSimConfig(WifiConfiguration config)331 public static boolean isSimConfig(WifiConfiguration config) { 332 return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE; 333 } 334 335 /** 336 * Returns true if {@code identity} contains an anonymous@realm identity, false otherwise. 337 */ isAnonymousAtRealmIdentity(String identity)338 public static boolean isAnonymousAtRealmIdentity(String identity) { 339 if (identity == null) return false; 340 return identity.startsWith(TelephonyUtil.ANONYMOUS_IDENTITY + "@"); 341 } 342 343 /** 344 * Checks if the EAP outer method is SIM related. 345 * 346 * @param eapMethod WifiEnterpriseConfig Eap method. 347 * @return true if this EAP outer method is SIM-related, false otherwise. 348 */ isSimEapMethod(int eapMethod)349 public static boolean isSimEapMethod(int eapMethod) { 350 return eapMethod == WifiEnterpriseConfig.Eap.SIM 351 || eapMethod == WifiEnterpriseConfig.Eap.AKA 352 || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME; 353 } 354 355 // TODO replace some of this code with Byte.parseByte parseHex(char ch)356 private static int parseHex(char ch) { 357 if ('0' <= ch && ch <= '9') { 358 return ch - '0'; 359 } else if ('a' <= ch && ch <= 'f') { 360 return ch - 'a' + 10; 361 } else if ('A' <= ch && ch <= 'F') { 362 return ch - 'A' + 10; 363 } else { 364 throw new NumberFormatException("" + ch + " is not a valid hex digit"); 365 } 366 } 367 parseHex(String hex)368 private static byte[] parseHex(String hex) { 369 /* This only works for good input; don't throw bad data at it */ 370 if (hex == null) { 371 return new byte[0]; 372 } 373 374 if (hex.length() % 2 != 0) { 375 throw new NumberFormatException(hex + " is not a valid hex string"); 376 } 377 378 byte[] result = new byte[(hex.length()) / 2 + 1]; 379 result[0] = (byte) ((hex.length()) / 2); 380 for (int i = 0, j = 1; i < hex.length(); i += 2, j++) { 381 int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1)); 382 byte b = (byte) (val & 0xFF); 383 result[j] = b; 384 } 385 386 return result; 387 } 388 parseHexWithoutLength(String hex)389 private static byte[] parseHexWithoutLength(String hex) { 390 byte[] tmpRes = parseHex(hex); 391 if (tmpRes.length == 0) { 392 return tmpRes; 393 } 394 395 byte[] result = new byte[tmpRes.length - 1]; 396 System.arraycopy(tmpRes, 1, result, 0, tmpRes.length - 1); 397 398 return result; 399 } 400 makeHex(byte[] bytes)401 private static String makeHex(byte[] bytes) { 402 StringBuilder sb = new StringBuilder(); 403 for (byte b : bytes) { 404 sb.append(String.format("%02x", b)); 405 } 406 return sb.toString(); 407 } 408 makeHex(byte[] bytes, int from, int len)409 private static String makeHex(byte[] bytes, int from, int len) { 410 StringBuilder sb = new StringBuilder(); 411 for (int i = 0; i < len; i++) { 412 sb.append(String.format("%02x", bytes[from + i])); 413 } 414 return sb.toString(); 415 } 416 concatHex(byte[] array1, byte[] array2)417 private static byte[] concatHex(byte[] array1, byte[] array2) { 418 419 int len = array1.length + array2.length; 420 421 byte[] result = new byte[len]; 422 423 int index = 0; 424 if (array1.length != 0) { 425 for (byte b : array1) { 426 result[index] = b; 427 index++; 428 } 429 } 430 431 if (array2.length != 0) { 432 for (byte b : array2) { 433 result[index] = b; 434 index++; 435 } 436 } 437 438 return result; 439 } 440 441 /** 442 * Calculate SRES and KC as 3G authentication. 443 * 444 * Standard Cellular_auth Type Command 445 * 446 * 3GPP TS 31.102 3G_authentication [Length][RAND][Length][AUTN] 447 * [Length][RES][Length][CK][Length][IK] and more 448 * 449 * @param requestData RAND data from server. 450 * @param tm the instance of TelephonyManager. 451 * @return the response data processed by SIM. If all request data is malformed, then returns 452 * empty string. If request data is invalid, then returns null. 453 */ getGsmSimAuthResponse(String[] requestData, TelephonyManager tm)454 public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) { 455 return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_USIM); 456 } 457 458 /** 459 * Calculate SRES and KC as 2G authentication. 460 * 461 * Standard Cellular_auth Type Command 462 * 463 * 3GPP TS 31.102 2G_authentication [Length][RAND] 464 * [Length][SRES][Length][Cipher Key Kc] 465 * 466 * @param requestData RAND data from server. 467 * @param tm the instance of TelephonyManager. 468 * @return the response data processed by SIM. If all request data is malformed, then returns 469 * empty string. If request data is invalid, then returns null. 470 */ getGsmSimpleSimAuthResponse(String[] requestData, TelephonyManager tm)471 public static String getGsmSimpleSimAuthResponse(String[] requestData, TelephonyManager tm) { 472 return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_SIM); 473 } 474 getGsmAuthResponseWithLength(String[] requestData, TelephonyManager tm, int appType)475 private static String getGsmAuthResponseWithLength(String[] requestData, TelephonyManager tm, 476 int appType) { 477 if (tm == null) { 478 Log.e(TAG, "No valid TelephonyManager"); 479 return null; 480 } 481 TelephonyManager defaultDataTm = tm.createForSubscriptionId( 482 SubscriptionManager.getDefaultDataSubscriptionId()); 483 StringBuilder sb = new StringBuilder(); 484 for (String challenge : requestData) { 485 if (challenge == null || challenge.isEmpty()) { 486 continue; 487 } 488 Log.d(TAG, "RAND = " + challenge); 489 490 byte[] rand = null; 491 try { 492 rand = parseHex(challenge); 493 } catch (NumberFormatException e) { 494 Log.e(TAG, "malformed challenge"); 495 continue; 496 } 497 498 String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP); 499 500 String tmResponse = defaultDataTm.getIccAuthentication( 501 appType, TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge); 502 Log.v(TAG, "Raw Response - " + tmResponse); 503 504 if (tmResponse == null || tmResponse.length() <= 4) { 505 Log.e(TAG, "bad response - " + tmResponse); 506 return null; 507 } 508 509 byte[] result = Base64.decode(tmResponse, Base64.DEFAULT); 510 Log.v(TAG, "Hex Response -" + makeHex(result)); 511 int sresLen = result[0]; 512 if (sresLen < 0 || sresLen >= result.length) { 513 Log.e(TAG, "malformed response - " + tmResponse); 514 return null; 515 } 516 String sres = makeHex(result, 1, sresLen); 517 int kcOffset = 1 + sresLen; 518 if (kcOffset >= result.length) { 519 Log.e(TAG, "malformed response - " + tmResponse); 520 return null; 521 } 522 int kcLen = result[kcOffset]; 523 if (kcLen < 0 || kcOffset + kcLen > result.length) { 524 Log.e(TAG, "malformed response - " + tmResponse); 525 return null; 526 } 527 String kc = makeHex(result, 1 + kcOffset, kcLen); 528 sb.append(":" + kc + ":" + sres); 529 Log.v(TAG, "kc:" + kc + " sres:" + sres); 530 } 531 532 return sb.toString(); 533 } 534 535 /** 536 * Calculate SRES and KC as 2G authentication. 537 * 538 * Standard Cellular_auth Type Command 539 * 540 * 3GPP TS 11.11 2G_authentication [RAND] 541 * [SRES][Cipher Key Kc] 542 * 543 * @param requestData RAND data from server. 544 * @param tm the instance of TelephonyManager. 545 * @return the response data processed by SIM. If all request data is malformed, then returns 546 * empty string. If request data is invalid, then returns null. 547 */ getGsmSimpleSimNoLengthAuthResponse(String[] requestData, TelephonyManager tm)548 public static String getGsmSimpleSimNoLengthAuthResponse(String[] requestData, 549 TelephonyManager tm) { 550 if (tm == null) { 551 Log.e(TAG, "No valid TelephonyManager"); 552 return null; 553 } 554 TelephonyManager defaultDataTm = tm.createForSubscriptionId( 555 SubscriptionManager.getDefaultDataSubscriptionId()); 556 StringBuilder sb = new StringBuilder(); 557 for (String challenge : requestData) { 558 if (challenge == null || challenge.isEmpty()) { 559 continue; 560 } 561 Log.d(TAG, "RAND = " + challenge); 562 563 byte[] rand = null; 564 try { 565 rand = parseHexWithoutLength(challenge); 566 } catch (NumberFormatException e) { 567 Log.e(TAG, "malformed challenge"); 568 continue; 569 } 570 571 String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP); 572 573 String tmResponse = defaultDataTm.getIccAuthentication(TelephonyManager.APPTYPE_SIM, 574 TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge); 575 Log.v(TAG, "Raw Response - " + tmResponse); 576 577 if (tmResponse == null || tmResponse.length() <= 4) { 578 Log.e(TAG, "bad response - " + tmResponse); 579 return null; 580 } 581 582 byte[] result = Base64.decode(tmResponse, Base64.DEFAULT); 583 if (SRES_LEN + KC_LEN != result.length) { 584 Log.e(TAG, "malformed response - " + tmResponse); 585 return null; 586 } 587 Log.v(TAG, "Hex Response -" + makeHex(result)); 588 String sres = makeHex(result, START_SRES_POS, SRES_LEN); 589 String kc = makeHex(result, START_KC_POS, KC_LEN); 590 sb.append(":" + kc + ":" + sres); 591 Log.v(TAG, "kc:" + kc + " sres:" + sres); 592 } 593 594 return sb.toString(); 595 } 596 597 /** 598 * Data supplied when making a SIM Auth Request 599 */ 600 public static class SimAuthRequestData { SimAuthRequestData()601 public SimAuthRequestData() {} SimAuthRequestData(int networkId, int protocol, String ssid, String[] data)602 public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) { 603 this.networkId = networkId; 604 this.protocol = protocol; 605 this.ssid = ssid; 606 this.data = data; 607 } 608 609 public int networkId; 610 public int protocol; 611 public String ssid; 612 // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges 613 // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge 614 public String[] data; 615 } 616 617 /** 618 * The response to a SIM Auth request if successful 619 */ 620 public static class SimAuthResponseData { SimAuthResponseData(String type, String response)621 public SimAuthResponseData(String type, String response) { 622 this.type = type; 623 this.response = response; 624 } 625 626 public String type; 627 public String response; 628 } 629 get3GAuthResponse(SimAuthRequestData requestData, TelephonyManager tm)630 public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData, 631 TelephonyManager tm) { 632 StringBuilder sb = new StringBuilder(); 633 byte[] rand = null; 634 byte[] authn = null; 635 String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH; 636 637 if (requestData.data.length == 2) { 638 try { 639 rand = parseHex(requestData.data[0]); 640 authn = parseHex(requestData.data[1]); 641 } catch (NumberFormatException e) { 642 Log.e(TAG, "malformed challenge"); 643 } 644 } else { 645 Log.e(TAG, "malformed challenge"); 646 } 647 648 String tmResponse = ""; 649 if (rand != null && authn != null) { 650 String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP); 651 if (tm != null) { 652 tmResponse = tm 653 .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId()) 654 .getIccAuthentication(TelephonyManager.APPTYPE_USIM, 655 TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge); 656 Log.v(TAG, "Raw Response - " + tmResponse); 657 } else { 658 Log.e(TAG, "No valid TelephonyManager"); 659 } 660 } 661 662 boolean goodReponse = false; 663 if (tmResponse != null && tmResponse.length() > 4) { 664 byte[] result = Base64.decode(tmResponse, Base64.DEFAULT); 665 Log.e(TAG, "Hex Response - " + makeHex(result)); 666 byte tag = result[0]; 667 if (tag == (byte) 0xdb) { 668 Log.v(TAG, "successful 3G authentication "); 669 int resLen = result[1]; 670 String res = makeHex(result, 2, resLen); 671 int ckLen = result[resLen + 2]; 672 String ck = makeHex(result, resLen + 3, ckLen); 673 int ikLen = result[resLen + ckLen + 3]; 674 String ik = makeHex(result, resLen + ckLen + 4, ikLen); 675 sb.append(":" + ik + ":" + ck + ":" + res); 676 Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res); 677 goodReponse = true; 678 } else if (tag == (byte) 0xdc) { 679 Log.e(TAG, "synchronisation failure"); 680 int autsLen = result[1]; 681 String auts = makeHex(result, 2, autsLen); 682 resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS; 683 sb.append(":" + auts); 684 Log.v(TAG, "auts:" + auts); 685 goodReponse = true; 686 } else { 687 Log.e(TAG, "bad response - unknown tag = " + tag); 688 } 689 } else { 690 Log.e(TAG, "bad response - " + tmResponse); 691 } 692 693 if (goodReponse) { 694 String response = sb.toString(); 695 Log.v(TAG, "Supplicant Response -" + response); 696 return new SimAuthResponseData(resType, response); 697 } else { 698 return null; 699 } 700 } 701 702 /** 703 * Get the carrier type of current SIM. 704 * 705 * @param tm {@link TelephonyManager} instance 706 * @return carrier type of current active sim, {{@link #CARRIER_INVALID_TYPE}} if sim is not 707 * ready or {@code tm} is {@code null} 708 */ getCarrierType(@onNull TelephonyManager tm)709 public static int getCarrierType(@NonNull TelephonyManager tm) { 710 if (tm == null) { 711 return CARRIER_INVALID_TYPE; 712 } 713 TelephonyManager defaultDataTm = tm.createForSubscriptionId( 714 SubscriptionManager.getDefaultDataSubscriptionId()); 715 716 if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) { 717 return CARRIER_INVALID_TYPE; 718 } 719 720 // If two APIs return the same carrier ID, then is considered as MNO, otherwise MVNO 721 if (defaultDataTm.getCarrierIdFromSimMccMnc() == defaultDataTm.getSimCarrierId()) { 722 return CARRIER_MNO_TYPE; 723 } 724 return CARRIER_MVNO_TYPE; 725 } 726 727 /** 728 * Returns true if at least one SIM is present on the device, false otherwise. 729 */ isSimPresent(@onnull SubscriptionManager sm)730 public static boolean isSimPresent(@Nonnull SubscriptionManager sm) { 731 return sm.getActiveSubscriptionIdList().length > 0; 732 } 733 } 734