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