/*
|
* Copyright (C) 2016 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.server.wifi.util;
|
|
import android.annotation.NonNull;
|
import android.net.wifi.WifiConfiguration;
|
import android.net.wifi.WifiEnterpriseConfig;
|
import android.telephony.ImsiEncryptionInfo;
|
import android.telephony.SubscriptionManager;
|
import android.telephony.TelephonyManager;
|
import android.util.Base64;
|
import android.util.Log;
|
import android.util.Pair;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.server.wifi.CarrierNetworkConfig;
|
import com.android.server.wifi.WifiNative;
|
|
import java.security.InvalidKeyException;
|
import java.security.NoSuchAlgorithmException;
|
import java.security.PublicKey;
|
import java.util.HashMap;
|
|
import javax.annotation.Nonnull;
|
import javax.crypto.BadPaddingException;
|
import javax.crypto.Cipher;
|
import javax.crypto.IllegalBlockSizeException;
|
import javax.crypto.NoSuchPaddingException;
|
|
/**
|
* Utilities for the Wifi Service to interact with telephony.
|
* TODO(b/132188983): Refactor into TelephonyFacade which owns all instances of
|
* TelephonyManager/SubscriptionManager in Wifi
|
*/
|
public class TelephonyUtil {
|
public static final String TAG = "TelephonyUtil";
|
public static final String DEFAULT_EAP_PREFIX = "\0";
|
|
public static final int CARRIER_INVALID_TYPE = -1;
|
public static final int CARRIER_MNO_TYPE = 0; // Mobile Network Operator
|
public static final int CARRIER_MVNO_TYPE = 1; // Mobile Virtual Network Operator
|
public static final String ANONYMOUS_IDENTITY = "anonymous";
|
public static final String THREE_GPP_NAI_REALM_FORMAT = "wlan.mnc%s.mcc%s.3gppnetwork.org";
|
|
// IMSI encryption method: RSA-OAEP with SHA-256 hash function
|
private static final String IMSI_CIPHER_TRANSFORMATION =
|
"RSA/ECB/OAEPwithSHA-256andMGF1Padding";
|
|
private static final HashMap<Integer, String> EAP_METHOD_PREFIX = new HashMap<>();
|
static {
|
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA, "0");
|
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.SIM, "1");
|
EAP_METHOD_PREFIX.put(WifiEnterpriseConfig.Eap.AKA_PRIME, "6");
|
}
|
|
/**
|
* 3GPP TS 11.11 2G_authentication command/response
|
* Input: [RAND]
|
* Output: [SRES][Cipher Key Kc]
|
*/
|
private static final int START_SRES_POS = 0; // MUST be 0
|
private static final int SRES_LEN = 4;
|
private static final int START_KC_POS = START_SRES_POS + SRES_LEN;
|
private static final int KC_LEN = 8;
|
|
/**
|
* Get the identity for the current SIM or null if the SIM is not available
|
*
|
* @param tm TelephonyManager instance
|
* @param config WifiConfiguration that indicates what sort of authentication is necessary
|
* @param telephonyUtil TelephonyUtil instance
|
* @param carrierNetworkConfig CarrierNetworkConfig instance
|
* @return Pair<identify, encrypted identity> or null if the SIM is not available
|
* or config is invalid
|
*/
|
public static Pair<String, String> getSimIdentity(TelephonyManager tm,
|
TelephonyUtil telephonyUtil,
|
WifiConfiguration config, CarrierNetworkConfig carrierNetworkConfig) {
|
if (tm == null) {
|
Log.e(TAG, "No valid TelephonyManager");
|
return null;
|
}
|
TelephonyManager defaultDataTm = tm.createForSubscriptionId(
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
if (carrierNetworkConfig == null) {
|
Log.e(TAG, "No valid CarrierNetworkConfig");
|
return null;
|
}
|
String imsi = defaultDataTm.getSubscriberId();
|
String mccMnc = "";
|
|
if (defaultDataTm.getSimState() == TelephonyManager.SIM_STATE_READY) {
|
mccMnc = defaultDataTm.getSimOperator();
|
}
|
|
String identity = buildIdentity(getSimMethodForConfig(config), imsi, mccMnc, false);
|
if (identity == null) {
|
Log.e(TAG, "Failed to build the identity");
|
return null;
|
}
|
|
ImsiEncryptionInfo imsiEncryptionInfo;
|
try {
|
imsiEncryptionInfo = defaultDataTm.getCarrierInfoForImsiEncryption(
|
TelephonyManager.KEY_TYPE_WLAN);
|
} catch (RuntimeException e) {
|
Log.e(TAG, "Failed to get imsi encryption info: " + e.getMessage());
|
return null;
|
}
|
if (imsiEncryptionInfo == null) {
|
// Does not support encrypted identity.
|
return Pair.create(identity, "");
|
}
|
|
String encryptedIdentity = buildEncryptedIdentity(telephonyUtil, identity,
|
imsiEncryptionInfo);
|
|
// In case of failure for encryption, abort current EAP authentication.
|
if (encryptedIdentity == null) {
|
Log.e(TAG, "failed to encrypt the identity");
|
return null;
|
}
|
return Pair.create(identity, encryptedIdentity);
|
}
|
|
/**
|
* Gets Anonymous identity for current active SIM.
|
*
|
* @param tm TelephonyManager instance
|
* @return anonymous identity@realm which is based on current MCC/MNC, {@code null} if SIM is
|
* not ready or absent.
|
*/
|
public static String getAnonymousIdentityWith3GppRealm(@Nonnull TelephonyManager tm) {
|
if (tm == null) {
|
return null;
|
}
|
TelephonyManager defaultDataTm = tm.createForSubscriptionId(
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
|
return null;
|
}
|
String mccMnc = defaultDataTm.getSimOperator();
|
if (mccMnc == null || mccMnc.isEmpty()) {
|
return null;
|
}
|
|
// Extract mcc & mnc from mccMnc
|
String mcc = mccMnc.substring(0, 3);
|
String mnc = mccMnc.substring(3);
|
|
if (mnc.length() == 2) {
|
mnc = "0" + mnc;
|
}
|
|
String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
|
return ANONYMOUS_IDENTITY + "@" + realm;
|
}
|
|
/**
|
* Encrypt the given data with the given public key. The encrypted data will be returned as
|
* a Base64 encoded string.
|
*
|
* @param key The public key to use for encryption
|
* @param encodingFlag base64 encoding flag
|
* @return Base64 encoded string, or null if encryption failed
|
*/
|
@VisibleForTesting
|
public String encryptDataUsingPublicKey(PublicKey key, byte[] data, int encodingFlag) {
|
try {
|
Cipher cipher = Cipher.getInstance(IMSI_CIPHER_TRANSFORMATION);
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
byte[] encryptedBytes = cipher.doFinal(data);
|
|
return Base64.encodeToString(encryptedBytes, 0, encryptedBytes.length, encodingFlag);
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
| IllegalBlockSizeException | BadPaddingException e) {
|
Log.e(TAG, "Encryption failed: " + e.getMessage());
|
return null;
|
}
|
}
|
|
/**
|
* Create the encrypted identity.
|
*
|
* Prefix value:
|
* "0" - EAP-AKA Identity
|
* "1" - EAP-SIM Identity
|
* "6" - EAP-AKA' Identity
|
* Encrypted identity format: prefix|IMSI@<NAIRealm>
|
* @param telephonyUtil TelephonyUtil instance
|
* @param identity permanent identity with format based on section 4.1.1.6 of RFC 4187
|
* and 4.2.1.6 of RFC 4186.
|
* @param imsiEncryptionInfo The IMSI encryption info retrieved from the SIM
|
* @return "\0" + encryptedIdentity + "{, Key Identifier AVP}"
|
*/
|
private static String buildEncryptedIdentity(TelephonyUtil telephonyUtil, String identity,
|
ImsiEncryptionInfo imsiEncryptionInfo) {
|
if (imsiEncryptionInfo == null) {
|
Log.e(TAG, "imsiEncryptionInfo is not valid");
|
return null;
|
}
|
if (identity == null) {
|
Log.e(TAG, "identity is not valid");
|
return null;
|
}
|
|
// Build and return the encrypted identity.
|
String encryptedIdentity = telephonyUtil.encryptDataUsingPublicKey(
|
imsiEncryptionInfo.getPublicKey(), identity.getBytes(), Base64.NO_WRAP);
|
if (encryptedIdentity == null) {
|
Log.e(TAG, "Failed to encrypt IMSI");
|
return null;
|
}
|
encryptedIdentity = DEFAULT_EAP_PREFIX + encryptedIdentity;
|
if (imsiEncryptionInfo.getKeyIdentifier() != null) {
|
// Include key identifier AVP (Attribute Value Pair).
|
encryptedIdentity = encryptedIdentity + "," + imsiEncryptionInfo.getKeyIdentifier();
|
}
|
return encryptedIdentity;
|
}
|
|
/**
|
* Create an identity used for SIM-based EAP authentication. The identity will be based on
|
* the info retrieved from the SIM card, such as IMSI and IMSI encryption info. The IMSI
|
* contained in the identity will be encrypted if IMSI encryption info is provided.
|
*
|
* See rfc4186 & rfc4187 & rfc5448:
|
*
|
* Identity format:
|
* Prefix | [IMSI || Encrypted IMSI] | @realm | {, Key Identifier AVP}
|
* where "|" denotes concatenation, "||" denotes exclusive value, "{}"
|
* denotes optional value, and realm is the 3GPP network domain name derived from the given
|
* MCC/MNC according to the 3GGP spec(TS23.003).
|
*
|
* Prefix value:
|
* "\0" - Encrypted Identity
|
* "0" - EAP-AKA Identity
|
* "1" - EAP-SIM Identity
|
* "6" - EAP-AKA' Identity
|
*
|
* Encrypted IMSI:
|
* Base64{RSA_Public_Key_Encryption{eapPrefix | IMSI}}
|
* where "|" denotes concatenation,
|
*
|
* @param eapMethod EAP authentication method: EAP-SIM, EAP-AKA, EAP-AKA'
|
* @param imsi The IMSI retrieved from the SIM
|
* @param mccMnc The MCC MNC identifier retrieved from the SIM
|
* @param isEncrypted Whether the imsi is encrypted or not.
|
* @return the eap identity, built using either the encrypted or un-encrypted IMSI.
|
*/
|
private static String buildIdentity(int eapMethod, String imsi, String mccMnc,
|
boolean isEncrypted) {
|
if (imsi == null || imsi.isEmpty()) {
|
Log.e(TAG, "No IMSI or IMSI is null");
|
return null;
|
}
|
|
String prefix = isEncrypted ? DEFAULT_EAP_PREFIX : EAP_METHOD_PREFIX.get(eapMethod);
|
if (prefix == null) {
|
return null;
|
}
|
|
/* extract mcc & mnc from mccMnc */
|
String mcc;
|
String mnc;
|
if (mccMnc != null && !mccMnc.isEmpty()) {
|
mcc = mccMnc.substring(0, 3);
|
mnc = mccMnc.substring(3);
|
if (mnc.length() == 2) {
|
mnc = "0" + mnc;
|
}
|
} else {
|
// extract mcc & mnc from IMSI, assume mnc size is 3
|
mcc = imsi.substring(0, 3);
|
mnc = imsi.substring(3, 6);
|
}
|
|
String naiRealm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
|
return prefix + imsi + "@" + naiRealm;
|
}
|
|
/**
|
* Return the associated SIM method for the configuration.
|
*
|
* @param config WifiConfiguration corresponding to the network.
|
* @return the outer EAP method associated with this SIM configuration.
|
*/
|
private static int getSimMethodForConfig(WifiConfiguration config) {
|
if (config == null || config.enterpriseConfig == null) {
|
return WifiEnterpriseConfig.Eap.NONE;
|
}
|
int eapMethod = config.enterpriseConfig.getEapMethod();
|
if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) {
|
// Translate known inner eap methods into an equivalent outer eap method.
|
switch (config.enterpriseConfig.getPhase2Method()) {
|
case WifiEnterpriseConfig.Phase2.SIM:
|
eapMethod = WifiEnterpriseConfig.Eap.SIM;
|
break;
|
case WifiEnterpriseConfig.Phase2.AKA:
|
eapMethod = WifiEnterpriseConfig.Eap.AKA;
|
break;
|
case WifiEnterpriseConfig.Phase2.AKA_PRIME:
|
eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
|
break;
|
}
|
}
|
|
return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE;
|
}
|
|
/**
|
* Checks if the network is a SIM config.
|
*
|
* @param config Config corresponding to the network.
|
* @return true if it is a SIM config, false otherwise.
|
*/
|
public static boolean isSimConfig(WifiConfiguration config) {
|
return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE;
|
}
|
|
/**
|
* Returns true if {@code identity} contains an anonymous@realm identity, false otherwise.
|
*/
|
public static boolean isAnonymousAtRealmIdentity(String identity) {
|
if (identity == null) return false;
|
return identity.startsWith(TelephonyUtil.ANONYMOUS_IDENTITY + "@");
|
}
|
|
/**
|
* Checks if the EAP outer method is SIM related.
|
*
|
* @param eapMethod WifiEnterpriseConfig Eap method.
|
* @return true if this EAP outer method is SIM-related, false otherwise.
|
*/
|
public static boolean isSimEapMethod(int eapMethod) {
|
return eapMethod == WifiEnterpriseConfig.Eap.SIM
|
|| eapMethod == WifiEnterpriseConfig.Eap.AKA
|
|| eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
|
}
|
|
// TODO replace some of this code with Byte.parseByte
|
private static int parseHex(char ch) {
|
if ('0' <= ch && ch <= '9') {
|
return ch - '0';
|
} else if ('a' <= ch && ch <= 'f') {
|
return ch - 'a' + 10;
|
} else if ('A' <= ch && ch <= 'F') {
|
return ch - 'A' + 10;
|
} else {
|
throw new NumberFormatException("" + ch + " is not a valid hex digit");
|
}
|
}
|
|
private static byte[] parseHex(String hex) {
|
/* This only works for good input; don't throw bad data at it */
|
if (hex == null) {
|
return new byte[0];
|
}
|
|
if (hex.length() % 2 != 0) {
|
throw new NumberFormatException(hex + " is not a valid hex string");
|
}
|
|
byte[] result = new byte[(hex.length()) / 2 + 1];
|
result[0] = (byte) ((hex.length()) / 2);
|
for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
|
int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1));
|
byte b = (byte) (val & 0xFF);
|
result[j] = b;
|
}
|
|
return result;
|
}
|
|
private static byte[] parseHexWithoutLength(String hex) {
|
byte[] tmpRes = parseHex(hex);
|
if (tmpRes.length == 0) {
|
return tmpRes;
|
}
|
|
byte[] result = new byte[tmpRes.length - 1];
|
System.arraycopy(tmpRes, 1, result, 0, tmpRes.length - 1);
|
|
return result;
|
}
|
|
private static String makeHex(byte[] bytes) {
|
StringBuilder sb = new StringBuilder();
|
for (byte b : bytes) {
|
sb.append(String.format("%02x", b));
|
}
|
return sb.toString();
|
}
|
|
private static String makeHex(byte[] bytes, int from, int len) {
|
StringBuilder sb = new StringBuilder();
|
for (int i = 0; i < len; i++) {
|
sb.append(String.format("%02x", bytes[from + i]));
|
}
|
return sb.toString();
|
}
|
|
private static byte[] concatHex(byte[] array1, byte[] array2) {
|
|
int len = array1.length + array2.length;
|
|
byte[] result = new byte[len];
|
|
int index = 0;
|
if (array1.length != 0) {
|
for (byte b : array1) {
|
result[index] = b;
|
index++;
|
}
|
}
|
|
if (array2.length != 0) {
|
for (byte b : array2) {
|
result[index] = b;
|
index++;
|
}
|
}
|
|
return result;
|
}
|
|
/**
|
* Calculate SRES and KC as 3G authentication.
|
*
|
* Standard Cellular_auth Type Command
|
*
|
* 3GPP TS 31.102 3G_authentication [Length][RAND][Length][AUTN]
|
* [Length][RES][Length][CK][Length][IK] and more
|
*
|
* @param requestData RAND data from server.
|
* @param tm the instance of TelephonyManager.
|
* @return the response data processed by SIM. If all request data is malformed, then returns
|
* empty string. If request data is invalid, then returns null.
|
*/
|
public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
|
return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_USIM);
|
}
|
|
/**
|
* Calculate SRES and KC as 2G authentication.
|
*
|
* Standard Cellular_auth Type Command
|
*
|
* 3GPP TS 31.102 2G_authentication [Length][RAND]
|
* [Length][SRES][Length][Cipher Key Kc]
|
*
|
* @param requestData RAND data from server.
|
* @param tm the instance of TelephonyManager.
|
* @return the response data processed by SIM. If all request data is malformed, then returns
|
* empty string. If request data is invalid, then returns null.
|
*/
|
public static String getGsmSimpleSimAuthResponse(String[] requestData, TelephonyManager tm) {
|
return getGsmAuthResponseWithLength(requestData, tm, TelephonyManager.APPTYPE_SIM);
|
}
|
|
private static String getGsmAuthResponseWithLength(String[] requestData, TelephonyManager tm,
|
int appType) {
|
if (tm == null) {
|
Log.e(TAG, "No valid TelephonyManager");
|
return null;
|
}
|
TelephonyManager defaultDataTm = tm.createForSubscriptionId(
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
StringBuilder sb = new StringBuilder();
|
for (String challenge : requestData) {
|
if (challenge == null || challenge.isEmpty()) {
|
continue;
|
}
|
Log.d(TAG, "RAND = " + challenge);
|
|
byte[] rand = null;
|
try {
|
rand = parseHex(challenge);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "malformed challenge");
|
continue;
|
}
|
|
String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
|
|
String tmResponse = defaultDataTm.getIccAuthentication(
|
appType, TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
|
Log.v(TAG, "Raw Response - " + tmResponse);
|
|
if (tmResponse == null || tmResponse.length() <= 4) {
|
Log.e(TAG, "bad response - " + tmResponse);
|
return null;
|
}
|
|
byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
|
Log.v(TAG, "Hex Response -" + makeHex(result));
|
int sresLen = result[0];
|
if (sresLen < 0 || sresLen >= result.length) {
|
Log.e(TAG, "malformed response - " + tmResponse);
|
return null;
|
}
|
String sres = makeHex(result, 1, sresLen);
|
int kcOffset = 1 + sresLen;
|
if (kcOffset >= result.length) {
|
Log.e(TAG, "malformed response - " + tmResponse);
|
return null;
|
}
|
int kcLen = result[kcOffset];
|
if (kcLen < 0 || kcOffset + kcLen > result.length) {
|
Log.e(TAG, "malformed response - " + tmResponse);
|
return null;
|
}
|
String kc = makeHex(result, 1 + kcOffset, kcLen);
|
sb.append(":" + kc + ":" + sres);
|
Log.v(TAG, "kc:" + kc + " sres:" + sres);
|
}
|
|
return sb.toString();
|
}
|
|
/**
|
* Calculate SRES and KC as 2G authentication.
|
*
|
* Standard Cellular_auth Type Command
|
*
|
* 3GPP TS 11.11 2G_authentication [RAND]
|
* [SRES][Cipher Key Kc]
|
*
|
* @param requestData RAND data from server.
|
* @param tm the instance of TelephonyManager.
|
* @return the response data processed by SIM. If all request data is malformed, then returns
|
* empty string. If request data is invalid, then returns null.
|
*/
|
public static String getGsmSimpleSimNoLengthAuthResponse(String[] requestData,
|
TelephonyManager tm) {
|
if (tm == null) {
|
Log.e(TAG, "No valid TelephonyManager");
|
return null;
|
}
|
TelephonyManager defaultDataTm = tm.createForSubscriptionId(
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
StringBuilder sb = new StringBuilder();
|
for (String challenge : requestData) {
|
if (challenge == null || challenge.isEmpty()) {
|
continue;
|
}
|
Log.d(TAG, "RAND = " + challenge);
|
|
byte[] rand = null;
|
try {
|
rand = parseHexWithoutLength(challenge);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "malformed challenge");
|
continue;
|
}
|
|
String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
|
|
String tmResponse = defaultDataTm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
|
TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
|
Log.v(TAG, "Raw Response - " + tmResponse);
|
|
if (tmResponse == null || tmResponse.length() <= 4) {
|
Log.e(TAG, "bad response - " + tmResponse);
|
return null;
|
}
|
|
byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
|
if (SRES_LEN + KC_LEN != result.length) {
|
Log.e(TAG, "malformed response - " + tmResponse);
|
return null;
|
}
|
Log.v(TAG, "Hex Response -" + makeHex(result));
|
String sres = makeHex(result, START_SRES_POS, SRES_LEN);
|
String kc = makeHex(result, START_KC_POS, KC_LEN);
|
sb.append(":" + kc + ":" + sres);
|
Log.v(TAG, "kc:" + kc + " sres:" + sres);
|
}
|
|
return sb.toString();
|
}
|
|
/**
|
* Data supplied when making a SIM Auth Request
|
*/
|
public static class SimAuthRequestData {
|
public SimAuthRequestData() {}
|
public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) {
|
this.networkId = networkId;
|
this.protocol = protocol;
|
this.ssid = ssid;
|
this.data = data;
|
}
|
|
public int networkId;
|
public int protocol;
|
public String ssid;
|
// EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
|
// EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
|
public String[] data;
|
}
|
|
/**
|
* The response to a SIM Auth request if successful
|
*/
|
public static class SimAuthResponseData {
|
public SimAuthResponseData(String type, String response) {
|
this.type = type;
|
this.response = response;
|
}
|
|
public String type;
|
public String response;
|
}
|
|
public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData,
|
TelephonyManager tm) {
|
StringBuilder sb = new StringBuilder();
|
byte[] rand = null;
|
byte[] authn = null;
|
String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH;
|
|
if (requestData.data.length == 2) {
|
try {
|
rand = parseHex(requestData.data[0]);
|
authn = parseHex(requestData.data[1]);
|
} catch (NumberFormatException e) {
|
Log.e(TAG, "malformed challenge");
|
}
|
} else {
|
Log.e(TAG, "malformed challenge");
|
}
|
|
String tmResponse = "";
|
if (rand != null && authn != null) {
|
String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP);
|
if (tm != null) {
|
tmResponse = tm
|
.createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId())
|
.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
|
TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
|
Log.v(TAG, "Raw Response - " + tmResponse);
|
} else {
|
Log.e(TAG, "No valid TelephonyManager");
|
}
|
}
|
|
boolean goodReponse = false;
|
if (tmResponse != null && tmResponse.length() > 4) {
|
byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
|
Log.e(TAG, "Hex Response - " + makeHex(result));
|
byte tag = result[0];
|
if (tag == (byte) 0xdb) {
|
Log.v(TAG, "successful 3G authentication ");
|
int resLen = result[1];
|
String res = makeHex(result, 2, resLen);
|
int ckLen = result[resLen + 2];
|
String ck = makeHex(result, resLen + 3, ckLen);
|
int ikLen = result[resLen + ckLen + 3];
|
String ik = makeHex(result, resLen + ckLen + 4, ikLen);
|
sb.append(":" + ik + ":" + ck + ":" + res);
|
Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res);
|
goodReponse = true;
|
} else if (tag == (byte) 0xdc) {
|
Log.e(TAG, "synchronisation failure");
|
int autsLen = result[1];
|
String auts = makeHex(result, 2, autsLen);
|
resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS;
|
sb.append(":" + auts);
|
Log.v(TAG, "auts:" + auts);
|
goodReponse = true;
|
} else {
|
Log.e(TAG, "bad response - unknown tag = " + tag);
|
}
|
} else {
|
Log.e(TAG, "bad response - " + tmResponse);
|
}
|
|
if (goodReponse) {
|
String response = sb.toString();
|
Log.v(TAG, "Supplicant Response -" + response);
|
return new SimAuthResponseData(resType, response);
|
} else {
|
return null;
|
}
|
}
|
|
/**
|
* Get the carrier type of current SIM.
|
*
|
* @param tm {@link TelephonyManager} instance
|
* @return carrier type of current active sim, {{@link #CARRIER_INVALID_TYPE}} if sim is not
|
* ready or {@code tm} is {@code null}
|
*/
|
public static int getCarrierType(@NonNull TelephonyManager tm) {
|
if (tm == null) {
|
return CARRIER_INVALID_TYPE;
|
}
|
TelephonyManager defaultDataTm = tm.createForSubscriptionId(
|
SubscriptionManager.getDefaultDataSubscriptionId());
|
|
if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
|
return CARRIER_INVALID_TYPE;
|
}
|
|
// If two APIs return the same carrier ID, then is considered as MNO, otherwise MVNO
|
if (defaultDataTm.getCarrierIdFromSimMccMnc() == defaultDataTm.getSimCarrierId()) {
|
return CARRIER_MNO_TYPE;
|
}
|
return CARRIER_MVNO_TYPE;
|
}
|
|
/**
|
* Returns true if at least one SIM is present on the device, false otherwise.
|
*/
|
public static boolean isSimPresent(@Nonnull SubscriptionManager sm) {
|
return sm.getActiveSubscriptionIdList().length > 0;
|
}
|
}
|