• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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