• 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 
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