1 /* 2 * Copyright (C) 2021 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.libraries.entitlement.eapaka; 18 19 import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE; 20 import static com.android.libraries.entitlement.eapaka.EapAkaChallenge.SUBTYPE_AKA_CHALLENGE; 21 import static com.android.libraries.entitlement.eapaka.EapAkaChallenge.TYPE_EAP_AKA; 22 import static com.google.common.base.Preconditions.checkNotNull; 23 import static com.google.common.base.Strings.nullToEmpty; 24 25 import android.content.Context; 26 import android.telephony.TelephonyManager; 27 import android.util.Base64; 28 import android.util.Log; 29 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.libraries.entitlement.ServiceEntitlementException; 34 import com.android.libraries.entitlement.utils.BytesConverter; 35 36 import java.security.InvalidKeyException; 37 import java.security.NoSuchAlgorithmException; 38 import java.util.Arrays; 39 40 import javax.crypto.Mac; 41 import javax.crypto.spec.SecretKeySpec; 42 43 /** 44 * Generates the response of EAP-AKA challenge. Refer to RFC 4187 Section 8.1 Message 45 * Format/RFC 3748 Session 4 EAP Packet Format. 46 */ 47 public class EapAkaResponse { 48 private static final String TAG = "ServiceEntitlement"; 49 50 private static final byte CODE_RESPONSE = 0x02; 51 private static final byte SUBTYPE_SYNC_FAILURE = 0x04; 52 private static final byte ATTRIBUTE_RES = 0x03; 53 private static final byte ATTRIBUTE_AUTS = 0x04; 54 private static final byte ATTRIBUTE_MAC = 0x0B; 55 private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1"; 56 private static final int SHA1_OUTPUT_LENGTH = 20; 57 private static final int MAC_LENGTH = 16; 58 59 // RFC 4187 Section 9.4 EAP-Response/AKA-Challenge 60 @Nullable private String mResponse; 61 // RFC 4187 Section 9.6 EAP-Response/AKA-Synchronization-Failure 62 @Nullable private String mSynchronizationFailureResponse; 63 EapAkaResponse()64 private EapAkaResponse() {} 65 66 /** Returns EAP-Response/AKA-Challenge, if authentication success. Otherwise {@code null}. */ 67 @Nullable response()68 public String response() { 69 return mResponse; 70 } 71 72 /** 73 * Returns EAP-Response/AKA-Synchronization-Failure, if synchronization failure detected. 74 * Otherwise {@code null}. 75 */ 76 @Nullable synchronizationFailureResponse()77 public String synchronizationFailureResponse() { 78 return mSynchronizationFailureResponse; 79 } 80 81 /** 82 * Returns EAP-AKA challenge response message which generated with SIM EAP-AKA authentication 83 * with network provided EAP-AKA challenge request message. 84 */ respondToEapAkaChallenge( Context context, int simSubscriptionId, EapAkaChallenge eapAkaChallenge, String eapAkaRealm)85 public static EapAkaResponse respondToEapAkaChallenge( 86 Context context, 87 int simSubscriptionId, 88 EapAkaChallenge eapAkaChallenge, 89 String eapAkaRealm) 90 throws ServiceEntitlementException { 91 TelephonyManager telephonyManager = 92 context.getSystemService(TelephonyManager.class) 93 .createForSubscriptionId(simSubscriptionId); 94 95 // process EAP-AKA authentication with SIM 96 String response = null; 97 try { 98 response = telephonyManager.getIccAuthentication(TelephonyManager.APPTYPE_USIM, 99 TelephonyManager.AUTHTYPE_EAP_AKA, 100 eapAkaChallenge.getSimAuthenticationRequest()); 101 } catch (UnsupportedOperationException e) { 102 throw new ServiceEntitlementException( 103 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 104 "UnsupportedOperationException" + e.toString()); 105 } 106 if (response == null) { 107 throw new ServiceEntitlementException( 108 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "EAP-AKA response is null!"); 109 } 110 111 EapAkaSecurityContext securityContext = EapAkaSecurityContext.from(response); 112 EapAkaResponse result = new EapAkaResponse(); 113 114 byte[] res = securityContext.getRes(); 115 byte[] ik = securityContext.getIk(); 116 byte[] ck = securityContext.getCk(); 117 byte[] auts = securityContext.getAuts(); 118 119 if (res != null && ik != null && ck != null) { // Success authentication 120 // generate master key - refer to RFC 4187, section 7. Key Generation 121 MasterKey mk = 122 MasterKey.create( 123 EapAkaApi.getImsiEap( 124 telephonyManager.getSimOperator(), 125 telephonyManager.getSubscriberId(), 126 eapAkaRealm), 127 ik, 128 ck); 129 // K_aut is the key used to calculate MAC 130 if (mk == null || mk.getAut() == null) { 131 throw new ServiceEntitlementException( 132 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, "Can't generate K_Aut!"); 133 } 134 135 // generate EAP-AKA challenge response message 136 byte[] challengeResponse = 137 generateEapAkaChallengeResponse( 138 res, eapAkaChallenge.getIdentifier(), mk.getAut()); 139 if (challengeResponse == null) { 140 throw new ServiceEntitlementException( 141 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 142 "Failed to generate EAP-AKA Challenge Response data!"); 143 } 144 // base64 encoding 145 result.mResponse = Base64.encodeToString(challengeResponse, Base64.NO_WRAP).trim(); 146 147 } else if (auts != null) { 148 byte[] syncFailure = 149 generateEapAkaSynchronizationFailureResponse( 150 auts, eapAkaChallenge.getIdentifier()); 151 result.mSynchronizationFailureResponse = 152 Base64.encodeToString(syncFailure, Base64.NO_WRAP).trim(); 153 154 } else { 155 throw new ServiceEntitlementException( 156 ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE, 157 "Invalid SIM EAP-AKA authentication response!"); 158 } 159 160 return result; 161 } 162 163 /** 164 * Returns EAP-Response/AKA-Challenge message, or {@code null} if failed to generate. 165 * Refer to RFC 4187 section 9.4 EAP-Response/AKA-Challenge. 166 */ 167 @VisibleForTesting 168 @Nullable generateEapAkaChallengeResponse( @ullable byte[] res, byte identifier, @Nullable byte[] aut)169 static byte[] generateEapAkaChallengeResponse( 170 @Nullable byte[] res, byte identifier, @Nullable byte[] aut) { 171 if (res == null || aut == null) { 172 return null; 173 } 174 175 byte[] message = createEapAkaChallengeResponse(res, identifier); 176 177 // use K_aut as key to calculate mac 178 byte[] mac = calculateMac(aut, message); 179 if (mac == null) { 180 return null; 181 } 182 183 // fill MAC value to the message 184 // The value start index is 8 + AT_RES (4 + res.length) + header of AT_MAC (4) 185 int index = 8 + 4 + res.length + 4; 186 System.arraycopy(mac, 0, message, index, mac.length); 187 188 return message; 189 } 190 191 /** 192 * Returns EAP-Response/AKA-Synchronization-Failure, or {@code null} if failed to generate. 193 * Refer to RFC 4187 section 9.6 EAP-Response/AKA-Synchronization-Failure. 194 */ 195 @VisibleForTesting generateEapAkaSynchronizationFailureResponse( byte[] auts, byte identifier)196 static byte[] generateEapAkaSynchronizationFailureResponse( 197 byte[] auts, byte identifier) { 198 // size = 8 (header) + 2 (attribute & length) + AUTS 199 byte[] message = new byte[10 + auts.length]; 200 201 // set up header 202 message[0] = CODE_RESPONSE; 203 // identifier: same as request 204 message[1] = identifier; 205 // length: include entire EAP-AKA message 206 byte[] lengthBytes = BytesConverter.convertIntegerTo4Bytes(message.length); 207 message[2] = lengthBytes[2]; 208 message[3] = lengthBytes[3]; 209 message[4] = TYPE_EAP_AKA; 210 message[5] = SUBTYPE_SYNC_FAILURE; 211 // reserved: 2 bytes 212 message[6] = 0x00; 213 message[7] = 0x00; 214 215 // set up AT_AUTS. RFC 4187, Section 10.9 AT_AUTS 216 message[8] = ATTRIBUTE_AUTS; 217 // length (in 4-bytes): 4, because AUTS is 14 bytes, plus the attribute (1 byte) and 218 // the length (1 byte). 219 message[9] = 0x04; 220 System.arraycopy(auts, 0, message, 10, auts.length); 221 return message; 222 } 223 224 // AT_MAC/AT_RES are must included in response message 225 // 226 // Reference RFC 4187 Section 8.1 Message Format 227 // RFC 4187 Section 9.4 EAP-Response/AKA-Challenge 228 // RFC 3748 Section 4.1 Request and Response createEapAkaChallengeResponse(byte[] res, byte identifier)229 private static byte[] createEapAkaChallengeResponse(byte[] res, byte identifier) { 230 // size = 8 (header) + resHeader (4) + res.length + AT_MAC (20 bytes) 231 byte[] message = new byte[32 + res.length]; 232 233 // set up header 234 message[0] = CODE_RESPONSE; 235 // identifier: same as request 236 message[1] = identifier; 237 // length: include entire EAP-AKA message 238 byte[] lengthBytes = BytesConverter.convertIntegerTo4Bytes(message.length); 239 message[2] = lengthBytes[2]; 240 message[3] = lengthBytes[3]; 241 message[4] = TYPE_EAP_AKA; 242 message[5] = SUBTYPE_AKA_CHALLENGE; 243 // reserved: 2 bytes 244 message[6] = 0x00; 245 message[7] = 0x00; 246 247 int index = 8; 248 249 // set up AT_RES, RFC 4187, Section 10.8 AT_RES 250 message[index++] = ATTRIBUTE_RES; 251 // Length (in 4-bytes): 252 // The length of the RES should already be a multiple of 4 bytes. 253 // Add 4 to the attribute length to account for the attribute (1 byte), the length (1 byte), 254 // and the length of the RES in bits (2 bytes). 255 int resLength = (res.length + 4) / 4; 256 message[index++] = (byte) (resLength & 0xff); 257 // The value field of this attribute begins with the 2-byte RES Length, which identifies 258 // the exact length of the RES in bits. 259 byte[] resBitLength = BytesConverter.convertIntegerTo4Bytes(res.length * 8); 260 message[index++] = resBitLength[2]; 261 message[index++] = resBitLength[3]; 262 System.arraycopy(res, 0, message, index, res.length); 263 index += res.length; 264 265 // set up AT_MAC, RFC 4187, Section 10.15 AT_MAC 266 message[index++] = ATTRIBUTE_MAC; 267 // length (in 4-bytes): 5, because MAC is 16 bytes, plus the attribute (1 byte), 268 // the length (1 byte), and reserved bytes (2 bytes). 269 message[index++] = 0x05; 270 // With two bytes reserved 271 message[index++] = 0x00; 272 message[index++] = 0x00; 273 274 // The MAC is calculated over the whole EAP packet and concatenated with optional 275 // message-specific data, with the exception that the value field of the 276 // MAC attribute is set to zero when calculating the MAC. 277 Arrays.fill(message, index, index + 16, (byte) 0x00); 278 279 return message; 280 } 281 282 // See RFC 4187, 10.15 AT_MAC, snippet as below, the key must be k_aut 283 // 284 // The MAC algorithm is HMAC-SHA1-128 [RFC2104] keyed hash value. (The 285 // HMAC-SHA1-128 value is obtained from the 20-byte HMAC-SHA1 value by 286 // truncating the output to 16 bytes. Hence, the length of the MAC is 287 // 16 bytes.) The derivation of the authentication key (K_aut) used in 288 // the calculation of the MAC is specified in Section 7. 289 @Nullable calculateMac(byte[] key, byte[] message)290 private static byte[] calculateMac(byte[] key, byte[] message) { 291 try { 292 Mac mac = Mac.getInstance(ALGORITHM_HMAC_SHA1); 293 SecretKeySpec secret = new SecretKeySpec(key, ALGORITHM_HMAC_SHA1); 294 mac.init(secret); 295 byte[] output = mac.doFinal(message); 296 297 if (output == null || output.length != SHA1_OUTPUT_LENGTH) { 298 Log.e(TAG, "Invalid result! length should be 20, but " + output.length); 299 return null; 300 } 301 302 byte[] macValue = new byte[MAC_LENGTH]; 303 System.arraycopy(output, 0, macValue, 0, MAC_LENGTH); 304 return macValue; 305 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 306 Log.e(TAG, "calculateMac failed!", e); 307 } 308 309 return null; 310 } 311 } 312