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.net.wifi.WifiConfiguration; 20 import android.net.wifi.WifiEnterpriseConfig; 21 import android.telephony.TelephonyManager; 22 import android.util.Base64; 23 import android.util.Log; 24 25 import com.android.server.wifi.WifiNative; 26 27 /** 28 * Utilities for the Wifi Service to interact with telephony. 29 */ 30 public class TelephonyUtil { 31 public static final String TAG = "TelephonyUtil"; 32 33 /** 34 * Get the identity for the current SIM or null if the SIM is not available 35 * 36 * @param tm TelephonyManager instance 37 * @param config WifiConfiguration that indicates what sort of authentication is necessary 38 * @return String with the identity or none if the SIM is not available or config is invalid 39 */ 40 public static String getSimIdentity(TelephonyManager tm, WifiConfiguration config) { 41 if (tm == null) { 42 Log.e(TAG, "No valid TelephonyManager"); 43 return null; 44 } 45 String imsi = tm.getSubscriberId(); 46 String mccMnc = ""; 47 48 if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) { 49 mccMnc = tm.getSimOperator(); 50 } 51 52 return buildIdentity(getSimMethodForConfig(config), imsi, mccMnc); 53 } 54 55 /** 56 * create Permanent Identity base on IMSI, 57 * 58 * rfc4186 & rfc4187: 59 * identity = usernam@realm 60 * with username = prefix | IMSI 61 * and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003) 62 */ 63 private static String buildIdentity(int eapMethod, String imsi, String mccMnc) { 64 if (imsi == null || imsi.isEmpty()) { 65 Log.e(TAG, "No IMSI or IMSI is null"); 66 return null; 67 } 68 69 String prefix; 70 if (eapMethod == WifiEnterpriseConfig.Eap.SIM) { 71 prefix = "1"; 72 } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA) { 73 prefix = "0"; 74 } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) { 75 prefix = "6"; 76 } else { 77 Log.e(TAG, "Invalid EAP method"); 78 return null; 79 } 80 81 /* extract mcc & mnc from mccMnc */ 82 String mcc; 83 String mnc; 84 if (mccMnc != null && !mccMnc.isEmpty()) { 85 mcc = mccMnc.substring(0, 3); 86 mnc = mccMnc.substring(3); 87 if (mnc.length() == 2) { 88 mnc = "0" + mnc; 89 } 90 } else { 91 // extract mcc & mnc from IMSI, assume mnc size is 3 92 mcc = imsi.substring(0, 3); 93 mnc = imsi.substring(3, 6); 94 } 95 96 return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org"; 97 } 98 99 /** 100 * Return the associated SIM method for the configuration. 101 * 102 * @param config WifiConfiguration corresponding to the network. 103 * @return the outer EAP method associated with this SIM configuration. 104 */ 105 private static int getSimMethodForConfig(WifiConfiguration config) { 106 if (config == null || config.enterpriseConfig == null) { 107 return WifiEnterpriseConfig.Eap.NONE; 108 } 109 int eapMethod = config.enterpriseConfig.getEapMethod(); 110 if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) { 111 // Translate known inner eap methods into an equivalent outer eap method. 112 switch (config.enterpriseConfig.getPhase2Method()) { 113 case WifiEnterpriseConfig.Phase2.SIM: 114 eapMethod = WifiEnterpriseConfig.Eap.SIM; 115 break; 116 case WifiEnterpriseConfig.Phase2.AKA: 117 eapMethod = WifiEnterpriseConfig.Eap.AKA; 118 break; 119 case WifiEnterpriseConfig.Phase2.AKA_PRIME: 120 eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME; 121 break; 122 } 123 } 124 125 return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE; 126 } 127 128 /** 129 * Checks if the network is a SIM config. 130 * 131 * @param config Config corresponding to the network. 132 * @return true if it is a SIM config, false otherwise. 133 */ 134 public static boolean isSimConfig(WifiConfiguration config) { 135 return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE; 136 } 137 138 /** 139 * Checks if the EAP outer method is SIM related. 140 * 141 * @param eapMethod WifiEnterpriseConfig Eap method. 142 * @return true if this EAP outer method is SIM-related, false otherwise. 143 */ 144 public static boolean isSimEapMethod(int eapMethod) { 145 return eapMethod == WifiEnterpriseConfig.Eap.SIM 146 || eapMethod == WifiEnterpriseConfig.Eap.AKA 147 || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME; 148 } 149 150 // TODO replace some of this code with Byte.parseByte 151 private static int parseHex(char ch) { 152 if ('0' <= ch && ch <= '9') { 153 return ch - '0'; 154 } else if ('a' <= ch && ch <= 'f') { 155 return ch - 'a' + 10; 156 } else if ('A' <= ch && ch <= 'F') { 157 return ch - 'A' + 10; 158 } else { 159 throw new NumberFormatException("" + ch + " is not a valid hex digit"); 160 } 161 } 162 163 private static byte[] parseHex(String hex) { 164 /* This only works for good input; don't throw bad data at it */ 165 if (hex == null) { 166 return new byte[0]; 167 } 168 169 if (hex.length() % 2 != 0) { 170 throw new NumberFormatException(hex + " is not a valid hex string"); 171 } 172 173 byte[] result = new byte[(hex.length()) / 2 + 1]; 174 result[0] = (byte) ((hex.length()) / 2); 175 for (int i = 0, j = 1; i < hex.length(); i += 2, j++) { 176 int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1)); 177 byte b = (byte) (val & 0xFF); 178 result[j] = b; 179 } 180 181 return result; 182 } 183 184 private static String makeHex(byte[] bytes) { 185 StringBuilder sb = new StringBuilder(); 186 for (byte b : bytes) { 187 sb.append(String.format("%02x", b)); 188 } 189 return sb.toString(); 190 } 191 192 private static String makeHex(byte[] bytes, int from, int len) { 193 StringBuilder sb = new StringBuilder(); 194 for (int i = 0; i < len; i++) { 195 sb.append(String.format("%02x", bytes[from + i])); 196 } 197 return sb.toString(); 198 } 199 200 private static byte[] concatHex(byte[] array1, byte[] array2) { 201 202 int len = array1.length + array2.length; 203 204 byte[] result = new byte[len]; 205 206 int index = 0; 207 if (array1.length != 0) { 208 for (byte b : array1) { 209 result[index] = b; 210 index++; 211 } 212 } 213 214 if (array2.length != 0) { 215 for (byte b : array2) { 216 result[index] = b; 217 index++; 218 } 219 } 220 221 return result; 222 } 223 224 public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) { 225 if (tm == null) { 226 Log.e(TAG, "No valid TelephonyManager"); 227 return null; 228 } 229 StringBuilder sb = new StringBuilder(); 230 for (String challenge : requestData) { 231 if (challenge == null || challenge.isEmpty()) { 232 continue; 233 } 234 Log.d(TAG, "RAND = " + challenge); 235 236 byte[] rand = null; 237 try { 238 rand = parseHex(challenge); 239 } catch (NumberFormatException e) { 240 Log.e(TAG, "malformed challenge"); 241 continue; 242 } 243 244 String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP); 245 246 // Try USIM first for authentication. 247 String tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM, 248 TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge); 249 if (tmResponse == null) { 250 // Then, in case of failure, issue may be due to sim type, retry as a simple sim 251 tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM, 252 TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge); 253 } 254 Log.v(TAG, "Raw Response - " + tmResponse); 255 256 if (tmResponse == null || tmResponse.length() <= 4) { 257 Log.e(TAG, "bad response - " + tmResponse); 258 return null; 259 } 260 261 byte[] result = Base64.decode(tmResponse, Base64.DEFAULT); 262 Log.v(TAG, "Hex Response -" + makeHex(result)); 263 int sresLen = result[0]; 264 if (sresLen >= result.length) { 265 Log.e(TAG, "malfomed response - " + tmResponse); 266 return null; 267 } 268 String sres = makeHex(result, 1, sresLen); 269 int kcOffset = 1 + sresLen; 270 if (kcOffset >= result.length) { 271 Log.e(TAG, "malfomed response - " + tmResponse); 272 return null; 273 } 274 int kcLen = result[kcOffset]; 275 if (kcOffset + kcLen > result.length) { 276 Log.e(TAG, "malfomed response - " + tmResponse); 277 return null; 278 } 279 String kc = makeHex(result, 1 + kcOffset, kcLen); 280 sb.append(":" + kc + ":" + sres); 281 Log.v(TAG, "kc:" + kc + " sres:" + sres); 282 } 283 284 return sb.toString(); 285 } 286 287 /** 288 * Data supplied when making a SIM Auth Request 289 */ 290 public static class SimAuthRequestData { 291 public SimAuthRequestData() {} 292 public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) { 293 this.networkId = networkId; 294 this.protocol = protocol; 295 this.ssid = ssid; 296 this.data = data; 297 } 298 299 public int networkId; 300 public int protocol; 301 public String ssid; 302 // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges 303 // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge 304 public String[] data; 305 } 306 307 /** 308 * The response to a SIM Auth request if successful 309 */ 310 public static class SimAuthResponseData { 311 public SimAuthResponseData(String type, String response) { 312 this.type = type; 313 this.response = response; 314 } 315 316 public String type; 317 public String response; 318 } 319 320 public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData, 321 TelephonyManager tm) { 322 StringBuilder sb = new StringBuilder(); 323 byte[] rand = null; 324 byte[] authn = null; 325 String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH; 326 327 if (requestData.data.length == 2) { 328 try { 329 rand = parseHex(requestData.data[0]); 330 authn = parseHex(requestData.data[1]); 331 } catch (NumberFormatException e) { 332 Log.e(TAG, "malformed challenge"); 333 } 334 } else { 335 Log.e(TAG, "malformed challenge"); 336 } 337 338 String tmResponse = ""; 339 if (rand != null && authn != null) { 340 String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP); 341 if (tm != null) { 342 tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM, 343 TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge); 344 Log.v(TAG, "Raw Response - " + tmResponse); 345 } else { 346 Log.e(TAG, "No valid TelephonyManager"); 347 } 348 } 349 350 boolean goodReponse = false; 351 if (tmResponse != null && tmResponse.length() > 4) { 352 byte[] result = Base64.decode(tmResponse, Base64.DEFAULT); 353 Log.e(TAG, "Hex Response - " + makeHex(result)); 354 byte tag = result[0]; 355 if (tag == (byte) 0xdb) { 356 Log.v(TAG, "successful 3G authentication "); 357 int resLen = result[1]; 358 String res = makeHex(result, 2, resLen); 359 int ckLen = result[resLen + 2]; 360 String ck = makeHex(result, resLen + 3, ckLen); 361 int ikLen = result[resLen + ckLen + 3]; 362 String ik = makeHex(result, resLen + ckLen + 4, ikLen); 363 sb.append(":" + ik + ":" + ck + ":" + res); 364 Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res); 365 goodReponse = true; 366 } else if (tag == (byte) 0xdc) { 367 Log.e(TAG, "synchronisation failure"); 368 int autsLen = result[1]; 369 String auts = makeHex(result, 2, autsLen); 370 resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS; 371 sb.append(":" + auts); 372 Log.v(TAG, "auts:" + auts); 373 goodReponse = true; 374 } else { 375 Log.e(TAG, "bad response - unknown tag = " + tag); 376 } 377 } else { 378 Log.e(TAG, "bad response - " + tmResponse); 379 } 380 381 if (goodReponse) { 382 String response = sb.toString(); 383 Log.v(TAG, "Supplicant Response -" + response); 384 return new SimAuthResponseData(resType, response); 385 } else { 386 return null; 387 } 388 } 389 } 390