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