• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.internal.net.eap.statemachine;
18 
19 import static android.net.eap.EapSessionConfig.EapMethodConfig.EAP_TYPE_AKA_PRIME;
20 
21 import static com.android.internal.net.eap.EapAuthenticator.LOG;
22 import static com.android.internal.net.eap.message.simaka.EapAkaTypeData.EAP_AKA_CLIENT_ERROR;
23 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_AUTN;
24 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF;
25 import static com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.EAP_AT_KDF_INPUT;
26 
27 import android.annotation.Nullable;
28 import android.content.Context;
29 import android.net.eap.EapSessionConfig.EapAkaPrimeConfig;
30 import android.net.eap.EapSessionConfig.EapMethodConfig.EapMethod;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.internal.net.crypto.KeyGenerationUtils;
34 import com.android.internal.net.eap.EapResult;
35 import com.android.internal.net.eap.crypto.HmacSha256ByteSigner;
36 import com.android.internal.net.eap.message.EapMessage;
37 import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData;
38 import com.android.internal.net.eap.message.simaka.EapAkaPrimeTypeData.EapAkaPrimeTypeDataDecoder;
39 import com.android.internal.net.eap.message.simaka.EapAkaTypeData;
40 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute;
41 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtAutn;
42 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtClientErrorCode;
43 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdf;
44 import com.android.internal.net.eap.message.simaka.EapSimAkaAttribute.AtKdfInput;
45 import com.android.internal.net.eap.message.simaka.EapSimAkaTypeData.DecodeResult;
46 
47 import java.nio.BufferOverflowException;
48 import java.nio.BufferUnderflowException;
49 import java.nio.ByteBuffer;
50 import java.nio.charset.StandardCharsets;
51 import java.security.GeneralSecurityException;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Map;
55 
56 import javax.crypto.Mac;
57 import javax.crypto.spec.SecretKeySpec;
58 
59 /**
60  * EapAkaPrimeMethodStateMachine represents the valid paths possible for the EAP-AKA' protocol.
61  *
62  * <p>EAP-AKA' sessions will always follow the path:
63  *
64  * Created --+--> Identity --+--> Challenge  --> Final
65  *           |               |
66  *           +---------------+
67  *
68  * <p>Note: If the EAP-Request/AKA'-Challenge message contains an AUTN with an invalid sequence
69  * number, the peer will indicate a synchronization failure to the server and a new challenge will
70  * be attempted.
71  *
72  * <p>Note: EAP-Request/Notification messages can be received at any point in the above state
73  * machine At most one EAP-AKA'/Notification message is allowed per EAP-AKA' session.
74  *
75  * @see <a href="https://tools.ietf.org/html/rfc4187">RFC 4187, Extensible Authentication Protocol
76  *     for Authentication and Key Agreement (EAP-AKA)</a>
77  * @see <a href="https://tools.ietf.org/html/rfc5448">RFC 5448, Improved Extensible Authentication
78  *     Protocol Method for 3rd Generation Authentication and Key Agreement (EAP-AKA')</a>
79  * @hide
80  */
81 public class EapAkaPrimeMethodStateMachine extends EapAkaMethodStateMachine {
82     public static final int K_AUT_LEN = 32;
83     public static final int K_RE_LEN = 32;
84 
85     // EAP-AKA' identity prefix (RFC 5448#3)
86     private static final String AKA_PRIME_IDENTITY_PREFIX = "6";
87     private static final int SUPPORTED_KDF = 1;
88     private static final int FC = 0x20; // Required by TS 133 402 Annex A.2
89     private static final int SQN_XOR_AK_LEN = 6;
90     private static final int IK_PRIME_LENGTH = 16;
91     private static final int CK_PRIME_LENGTH = 16;
92     private static final String MAC_ALGORITHM_STRING = "HmacSHA256";
93     private static final String MK_DATA_PREFIX = "EAP-AKA'";
94 
95     // MK_LEN_BYTES = len(K_encr | K_aut | K_re | MSK | EMSK)
96     private static final int MK_LEN_BYTES =
97             KEY_LEN + K_AUT_LEN + K_RE_LEN + (2 * SESSION_KEY_LENGTH);
98 
99     public final byte[] mKRe = new byte[getKReLen()];
100 
101     private final EapAkaPrimeConfig mEapAkaPrimeConfig;
102     private final EapAkaPrimeTypeDataDecoder mEapAkaPrimeTypeDataDecoder;
103 
EapAkaPrimeMethodStateMachine( Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig)104     EapAkaPrimeMethodStateMachine(
105             Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig) {
106         this(
107                 context,
108                 eapIdentity,
109                 eapAkaPrimeConfig,
110                 EapAkaPrimeTypeData.getEapAkaPrimeTypeDataDecoder());
111     }
112 
113     @VisibleForTesting
EapAkaPrimeMethodStateMachine( Context context, byte[] eapIdentity, EapAkaPrimeConfig eapAkaPrimeConfig, EapAkaPrimeTypeDataDecoder eapAkaPrimeTypeDataDecoder)114     protected EapAkaPrimeMethodStateMachine(
115             Context context,
116             byte[] eapIdentity,
117             EapAkaPrimeConfig eapAkaPrimeConfig,
118             EapAkaPrimeTypeDataDecoder eapAkaPrimeTypeDataDecoder) {
119         super(context, eapIdentity, eapAkaPrimeConfig);
120         mEapAkaPrimeConfig = eapAkaPrimeConfig;
121         mEapAkaPrimeTypeDataDecoder = eapAkaPrimeTypeDataDecoder;
122 
123         transitionTo(new CreatedState());
124     }
125 
126     @Override
127     @EapMethod
getEapMethod()128     int getEapMethod() {
129         return EAP_TYPE_AKA_PRIME;
130     }
131 
132     @Override
getKAutLength()133     protected int getKAutLength() {
134         return K_AUT_LEN;
135     }
136 
getKReLen()137     protected int getKReLen() {
138         return K_RE_LEN;
139     }
140 
141     @Override
decode(byte[] typeData)142     protected DecodeResult<EapAkaTypeData> decode(byte[] typeData) {
143         return mEapAkaPrimeTypeDataDecoder.decode(typeData);
144     }
145 
146     @Override
getIdentityPrefix()147     protected String getIdentityPrefix() {
148         return AKA_PRIME_IDENTITY_PREFIX;
149     }
150 
151     @Override
buildChallengeState()152     protected ChallengeState buildChallengeState() {
153         return new ChallengeState();
154     }
155 
156     @Override
buildChallengeState(byte[] identity)157     protected ChallengeState buildChallengeState(byte[] identity) {
158         return new ChallengeState(identity);
159     }
160 
161     @Override
getMacAlgorithm()162     protected String getMacAlgorithm() {
163         return MAC_ALGORITHM_STRING;
164     }
165 
166     protected class ChallengeState extends EapAkaMethodStateMachine.ChallengeState {
167         private final String mTAG = ChallengeState.class.getSimpleName();
168 
ChallengeState()169         ChallengeState() {
170             super();
171         }
172 
ChallengeState(byte[] identity)173         ChallengeState(byte[] identity) {
174             super(identity);
175         }
176 
177         @Override
handleChallengeAuthentication( EapMessage message, EapAkaTypeData eapAkaTypeData)178         protected EapResult handleChallengeAuthentication(
179                 EapMessage message, EapAkaTypeData eapAkaTypeData) {
180             EapAkaPrimeTypeData eapAkaPrimeTypeData = (EapAkaPrimeTypeData) eapAkaTypeData;
181 
182             if (!isValidChallengeAttributes(eapAkaPrimeTypeData)) {
183                 return buildAuthenticationRejectMessage(message.eapIdentifier);
184             }
185             return super.handleChallengeAuthentication(message, eapAkaPrimeTypeData);
186         }
187 
188         @VisibleForTesting
isValidChallengeAttributes(EapAkaPrimeTypeData eapAkaPrimeTypeData)189         boolean isValidChallengeAttributes(EapAkaPrimeTypeData eapAkaPrimeTypeData) {
190             Map<Integer, EapSimAkaAttribute> attrs = eapAkaPrimeTypeData.attributeMap;
191 
192             if (!attrs.containsKey(EAP_AT_KDF) || !attrs.containsKey(EAP_AT_KDF_INPUT)) {
193                 return false;
194             }
195 
196             // TODO(b/143073851): implement KDF resolution specified in RFC 5448#3.2
197             // This is safe, as there only exists one defined KDF.
198             AtKdf atKdf = (AtKdf) attrs.get(EAP_AT_KDF);
199             if (atKdf.kdf != SUPPORTED_KDF) {
200                 return false;
201             }
202 
203             AtKdfInput atKdfInput = (AtKdfInput) attrs.get(EAP_AT_KDF_INPUT);
204             if (atKdfInput.networkName.length == 0) {
205                 return false;
206             }
207 
208             boolean hasMatchingNetworkNames =
209                     hasMatchingNetworkNames(
210                             mEapAkaPrimeConfig.getNetworkName(),
211                             new String(atKdfInput.networkName, StandardCharsets.UTF_8));
212             return mEapAkaPrimeConfig.allowsMismatchedNetworkNames() || hasMatchingNetworkNames;
213         }
214 
215         /**
216          * Compares the peer's network name against the server's network name.
217          *
218          * <p>RFC 5448#3.1 describes how the network names are to be compared: "each name is broken
219          * down to the fields separated by colons. If one of the names has more colons and fields
220          * than the other one, the additional fields are ignored. The remaining sequences of fields
221          * are compared. This algorithm allows a prefix match".
222          *
223          * @return true iff one network name matches the other, as defined by RC 5448#3.1
224          */
225         @VisibleForTesting
hasMatchingNetworkNames(String peerNetworkName, String serverNetworkName)226         boolean hasMatchingNetworkNames(String peerNetworkName, String serverNetworkName) {
227             // compare network names according to RFC 5448#3.1
228             if (peerNetworkName.isEmpty() || serverNetworkName.isEmpty()) {
229                 return true;
230             }
231 
232             String[] peerNetworkNameFields = peerNetworkName.split(":");
233             String[] serverNetworkNameFields = serverNetworkName.split(":");
234             int numFieldsToCompare =
235                     Math.min(peerNetworkNameFields.length, serverNetworkNameFields.length);
236             for (int i = 0; i < numFieldsToCompare; i++) {
237                 if (!peerNetworkNameFields[i].equals(serverNetworkNameFields[i])) {
238                     LOG.i(
239                             mTAG,
240                             "EAP-AKA' network names don't match."
241                                     + " Peer: " + LOG.pii(peerNetworkName)
242                                     + ", Server: " + LOG.pii(serverNetworkName));
243                     return false;
244                 }
245             }
246 
247             return true;
248         }
249 
250         @Nullable
251         @Override
generateAndPersistEapAkaKeys( RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData)252         protected EapResult generateAndPersistEapAkaKeys(
253                 RandChallengeResult result, int eapIdentifier, EapAkaTypeData eapAkaTypeData) {
254             try {
255                 AtKdfInput atKdfInput =
256                         (AtKdfInput) eapAkaTypeData.attributeMap.get(EAP_AT_KDF_INPUT);
257                 AtAutn atAutn = (AtAutn) eapAkaTypeData.attributeMap.get(EAP_AT_AUTN);
258 
259                 // Values derived as (CK' | IK'), but PRF' needs (IK' | CK')
260                 byte[] ckIkPrime = deriveCkIkPrime(result, atKdfInput, atAutn);
261                 ByteBuffer prfKey = ByteBuffer.allocate(IK_PRIME_LENGTH + CK_PRIME_LENGTH);
262                 prfKey.put(ckIkPrime, CK_PRIME_LENGTH, IK_PRIME_LENGTH);
263                 prfKey.put(ckIkPrime, 0, CK_PRIME_LENGTH);
264 
265                 int dataToSignLen = MK_DATA_PREFIX.length() + mIdentity.length;
266                 ByteBuffer dataToSign = ByteBuffer.allocate(dataToSignLen);
267                 dataToSign.put(MK_DATA_PREFIX.getBytes(StandardCharsets.US_ASCII));
268                 dataToSign.put(mIdentity);
269 
270                 ByteBuffer mk =
271                         ByteBuffer.wrap(
272                                 KeyGenerationUtils.prfPlus(
273                                         HmacSha256ByteSigner.getInstance(),
274                                         prfKey.array(),
275                                         dataToSign.array(),
276                                         MK_LEN_BYTES));
277 
278                 mk.get(mKEncr);
279                 mk.get(mKAut);
280                 mk.get(mKRe);
281                 mk.get(mMsk);
282                 mk.get(mEmsk);
283 
284                 // Log as hash unless PII debug mode enabled
285                 LOG.d(mTAG, "K_encr=" + LOG.pii(mKEncr));
286                 LOG.d(mTAG, "K_aut=" + LOG.pii(mKAut));
287                 LOG.d(mTAG, "K_re=" + LOG.pii(mKRe));
288                 LOG.d(mTAG, "MSK=" + LOG.pii(mMsk));
289                 LOG.d(mTAG, "EMSK=" + LOG.pii(mEmsk));
290                 return null;
291             } catch (GeneralSecurityException
292                     | BufferOverflowException
293                     | BufferUnderflowException ex) {
294                 LOG.e(mTAG, "Error while generating keys", ex);
295                 return buildClientErrorResponse(
296                         eapIdentifier, getEapMethod(), AtClientErrorCode.UNABLE_TO_PROCESS);
297             }
298         }
299 
300         /**
301          * Derives CK' and IK' values from CK and IK
302          *
303          * <p>CK' and IK' generation is specified in TS 133 402 Annex A.2, which relies on the key
304          * derivation function KDF specified in TS 133 220 Annex B.2.
305          */
306         @VisibleForTesting
deriveCkIkPrime( RandChallengeResult randChallengeResult, AtKdfInput atKdfInput, AtAutn atAutn)307         byte[] deriveCkIkPrime(
308                 RandChallengeResult randChallengeResult, AtKdfInput atKdfInput, AtAutn atAutn)
309                 throws GeneralSecurityException {
310             final int fcLen = 1;
311             int lengthFieldLen = 2;
312 
313             // SQN ^ AK is the first 6B of the AUTN value
314             byte[] sqnXorAk = Arrays.copyOf(atAutn.autn, SQN_XOR_AK_LEN);
315             int sLength =
316                     fcLen
317                             + atKdfInput.networkName.length + lengthFieldLen
318                             + SQN_XOR_AK_LEN + lengthFieldLen;
319 
320             ByteBuffer dataToSign = ByteBuffer.allocate(sLength);
321             dataToSign.put((byte) FC);
322             dataToSign.put(atKdfInput.networkName);
323             dataToSign.putShort((short) atKdfInput.networkName.length);
324             dataToSign.put(sqnXorAk);
325             dataToSign.putShort((short) SQN_XOR_AK_LEN);
326 
327             int keyLen = randChallengeResult.ck.length + randChallengeResult.ik.length;
328             ByteBuffer key = ByteBuffer.allocate(keyLen);
329             key.put(randChallengeResult.ck);
330             key.put(randChallengeResult.ik);
331 
332             Mac mac = Mac.getInstance(MAC_ALGORITHM_STRING);
333             mac.init(new SecretKeySpec(key.array(), MAC_ALGORITHM_STRING));
334             return mac.doFinal(dataToSign.array());
335         }
336     }
337 
getEapSimAkaTypeData(AtClientErrorCode clientErrorCode)338     EapAkaPrimeTypeData getEapSimAkaTypeData(AtClientErrorCode clientErrorCode) {
339         return new EapAkaPrimeTypeData(EAP_AKA_CLIENT_ERROR, Arrays.asList(clientErrorCode));
340     }
341 
getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes)342     EapAkaPrimeTypeData getEapSimAkaTypeData(int eapSubtype, List<EapSimAkaAttribute> attributes) {
343         return new EapAkaPrimeTypeData(eapSubtype, attributes);
344     }
345 }
346