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 
21 import static java.nio.charset.StandardCharsets.UTF_8;
22 
23 import android.util.Base64;
24 import android.util.Log;
25 
26 import androidx.annotation.Nullable;
27 
28 import com.android.libraries.entitlement.ServiceEntitlementException;
29 
30 /**
31  * Parses EAP-AKA challenge from server. Refer to RFC 4187 Section 8.1 Message
32  * Format/RFC 3748 Session 4 EAP Packet Format.
33  */
34 public class EapAkaChallenge {
35     private static final String TAG = "ServiceEntitlement";
36 
37     private static final int EAP_AKA_HEADER_LENGTH = 8;
38     private static final byte CODE_REQUEST = 0x01;
39     static final byte TYPE_EAP_AKA = 0x17;
40     static final byte SUBTYPE_AKA_CHALLENGE = 0x01;
41     private static final byte ATTRIBUTE_RAND = 0x01;
42     private static final byte ATTRIBUTE_AUTN = 0x02;
43     private static final int ATTRIBUTE_LENGTH = 20;
44     private static final int RAND_LENGTH = 16;
45     private static final int AUTN_LENGTH = 16;
46 
47     // The identifier of Response must same as Request
48     private byte mIdentifier = -1;
49     // The value of AT_AUTN, network authentication token
50     private byte[] mAutn;
51     // The value of AT_RAND, random challenge
52     private byte[] mRand;
53 
54     // Base64 encoded 3G security context for SIM Authentication request
55     private String mSimAuthenticationRequest;
56 
EapAkaChallenge()57     private EapAkaChallenge() {}
58 
59     /** Parses a EAP-AKA challenge request message encoded in base64. */
parseEapAkaChallenge(String challenge)60     public static EapAkaChallenge parseEapAkaChallenge(String challenge)
61             throws ServiceEntitlementException {
62         byte[] data;
63         try {
64             data = Base64.decode(challenge.getBytes(UTF_8), Base64.DEFAULT);
65         } catch (IllegalArgumentException illegalArgumentException) {
66             throw new ServiceEntitlementException(
67                     ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
68                     "EAP-AKA challenge is not a valid base64!");
69         }
70         EapAkaChallenge result = new EapAkaChallenge();
71         if (result.parseEapAkaHeader(data) && result.parseRandAndAutn(data)) {
72             result.mSimAuthenticationRequest = result.getSimAuthChallengeData();
73             return result;
74         } else {
75             throw new ServiceEntitlementException(
76                     ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
77                     "EAP-AKA challenge message is not valid");
78         }
79     }
80 
81     /**
82      * Returns the base64 encoded 3G security context for SIM Authentication request,
83      * or {@code null} if the EAP-AKA challenge is not valid.
84      */
85     @Nullable
getSimAuthenticationRequest()86     public String getSimAuthenticationRequest() {
87         return mSimAuthenticationRequest;
88     }
89 
90     /** Returns the EAP package identifier in the EAP-AKA challenge. */
getIdentifier()91     public byte getIdentifier() {
92         return mIdentifier;
93     }
94 
95     /**
96      * Parses EAP-AKA header, 8 bytes including 2 reserved bytes.
97      *
98      * @return {@code true} if success to parse the header of request data.
99      */
parseEapAkaHeader(byte[] data)100     private boolean parseEapAkaHeader(byte[] data) {
101         if (data.length < EAP_AKA_HEADER_LENGTH) {
102             return false;
103         }
104         // Code for EAP-request should be CODE_REQUEST
105         byte code = data[0];
106         // EAP package identifier
107         mIdentifier = data[1];
108         // Total length of full EAP-AKA message, include code, identifier, ...
109         int length = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
110         // Type for EAP-AKA should be TYPE_EAP_AKA
111         byte type = data[4];
112         // SubType for AKA-Challenge should be SUBTYPE_AKA_CHALLENGE
113         byte subType = data[5];
114 
115         // Validate header
116         if (code != CODE_REQUEST
117                 || length != data.length
118                 || type != TYPE_EAP_AKA
119                 || subType != SUBTYPE_AKA_CHALLENGE) {
120             Log.d(
121                     TAG,
122                     "Invalid EAP-AKA Header, code="
123                             + code
124                             + ", length="
125                             + length
126                             + ", real length="
127                             + data.length
128                             + ", type="
129                             + type
130                             + ", subType="
131                             + subType);
132             return false;
133         }
134 
135         return true;
136     }
137 
138     /**
139      * Parses AT_RAND and AT_AUTN. Refer to RFC 4187 section 10.6 AT_RAND/section 10.7 AT_AUTN.
140      *
141      * @return {@code true} if success to parse the RAND and AUTN data.
142      */
parseRandAndAutn(byte[] data)143     private boolean parseRandAndAutn(byte[] data) {
144         int index = EAP_AKA_HEADER_LENGTH;
145         while (index < data.length) {
146             int remainsLength = data.length - index;
147             if (remainsLength <= 2) {
148                 Log.d(TAG, "Error! remainsLength = " + remainsLength);
149                 return false;
150             }
151 
152             byte attributeType = data[index];
153 
154             // the length of this attribute in multiples of 4 bytes, include attribute type and
155             // length
156             int length = (data[index + 1] & 0xff) * 4;
157             if (length > remainsLength) {
158                 Log.d(TAG,
159                         "Length Error! length is " + length + " but only remains " + remainsLength);
160                 return false;
161             }
162 
163             // see RFC 4187 section 11 for attribute type
164             if (attributeType == ATTRIBUTE_RAND) {
165                 if (length != ATTRIBUTE_LENGTH) {
166                     Log.d(TAG, "AT_RAND length is " + length);
167                     return false;
168                 }
169                 mRand = new byte[RAND_LENGTH];
170                 System.arraycopy(data, index + 4, mRand, 0, RAND_LENGTH);
171             } else if (attributeType == ATTRIBUTE_AUTN) {
172                 if (length != ATTRIBUTE_LENGTH) {
173                     Log.d(TAG, "AT_AUTN length is " + length);
174                     return false;
175                 }
176                 mAutn = new byte[AUTN_LENGTH];
177                 System.arraycopy(data, index + 4, mAutn, 0, AUTN_LENGTH);
178             }
179 
180             index += length;
181         }
182 
183         // check has AT_RAND and AT_AUTH
184         if (mRand == null || mAutn == null) {
185             Log.d(TAG, "Invalid Type Datas!");
186             return false;
187         }
188 
189         return true;
190     }
191 
192     /**
193      * Returns Base64 encoded 3G security context for SIM Authentication request.
194      */
195     @Nullable
getSimAuthChallengeData()196     private String getSimAuthChallengeData() {
197         byte[] challengeData = new byte[RAND_LENGTH + AUTN_LENGTH + 2];
198         challengeData[0] = (byte) RAND_LENGTH;
199         System.arraycopy(mRand, 0, challengeData, 1, RAND_LENGTH);
200         challengeData[RAND_LENGTH + 1] = (byte) AUTN_LENGTH;
201         System.arraycopy(mAutn, 0, challengeData, RAND_LENGTH + 2, AUTN_LENGTH);
202 
203         return Base64.encodeToString(challengeData, Base64.NO_WRAP).trim();
204     }
205 }
206