/* * 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.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.telephony.TelephonyManager; import android.util.Base64; import android.util.Log; import com.android.server.wifi.WifiNative; /** * Utilities for the Wifi Service to interact with telephony. */ public class TelephonyUtil { public static final String TAG = "TelephonyUtil"; /** * 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 * @return String with the identity or none if the SIM is not available or config is invalid */ public static String getSimIdentity(TelephonyManager tm, WifiConfiguration config) { if (tm == null) { Log.e(TAG, "No valid TelephonyManager"); return null; } String imsi = tm.getSubscriberId(); String mccMnc = ""; if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) { mccMnc = tm.getSimOperator(); } return buildIdentity(getSimMethodForConfig(config), imsi, mccMnc); } /** * create Permanent Identity base on IMSI, * * rfc4186 & rfc4187: * identity = usernam@realm * with username = prefix | IMSI * and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003) */ private static String buildIdentity(int eapMethod, String imsi, String mccMnc) { if (imsi == null || imsi.isEmpty()) { Log.e(TAG, "No IMSI or IMSI is null"); return null; } String prefix; if (eapMethod == WifiEnterpriseConfig.Eap.SIM) { prefix = "1"; } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA) { prefix = "0"; } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) { prefix = "6"; } else { Log.e(TAG, "Invalid EAP method"); 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); } return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org"; } /** * 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; } /** * 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 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; } public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) { if (tm == null) { Log.e(TAG, "No valid TelephonyManager"); return null; } 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); // Try USIM first for authentication. String tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM, TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge); if (tmResponse == null) { // Then, in case of failure, issue may be due to sim type, retry as a simple sim tmResponse = tm.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); Log.v(TAG, "Hex Response -" + makeHex(result)); int sresLen = result[0]; if (sresLen >= result.length) { Log.e(TAG, "malfomed response - " + tmResponse); return null; } String sres = makeHex(result, 1, sresLen); int kcOffset = 1 + sresLen; if (kcOffset >= result.length) { Log.e(TAG, "malfomed response - " + tmResponse); return null; } int kcLen = result[kcOffset]; if (kcOffset + kcLen > result.length) { Log.e(TAG, "malfomed 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(); } /** * 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.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; } } }