• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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_TTLS;
20 
21 import static com.android.internal.net.eap.EapAuthenticator.LOG;
22 import static com.android.internal.net.eap.crypto.TlsSession.TLS_STATUS_CLOSED;
23 import static com.android.internal.net.eap.crypto.TlsSession.TLS_STATUS_FAILURE;
24 import static com.android.internal.net.eap.crypto.TlsSession.TLS_STATUS_SUCCESS;
25 import static com.android.internal.net.eap.crypto.TlsSession.TLS_STATUS_TUNNEL_ESTABLISHED;
26 import static com.android.internal.net.eap.message.EapData.EAP_IDENTITY;
27 import static com.android.internal.net.eap.message.EapData.EAP_NOTIFICATION;
28 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_FAILURE;
29 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_RESPONSE;
30 import static com.android.internal.net.eap.message.EapMessage.EAP_CODE_SUCCESS;
31 import static com.android.internal.net.eap.message.ttls.EapTtlsInboundFragmentationHelper.FRAGMENTATION_STATUS_ACK;
32 import static com.android.internal.net.eap.message.ttls.EapTtlsInboundFragmentationHelper.FRAGMENTATION_STATUS_ASSEMBLED;
33 import static com.android.internal.net.eap.message.ttls.EapTtlsInboundFragmentationHelper.FRAGMENTATION_STATUS_INVALID;
34 
35 import android.annotation.Nullable;
36 import android.content.Context;
37 import android.net.eap.EapSessionConfig.EapMethodConfig.EapMethod;
38 import android.net.eap.EapSessionConfig.EapTtlsConfig;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 import com.android.internal.net.eap.EapResult;
42 import com.android.internal.net.eap.EapResult.EapError;
43 import com.android.internal.net.eap.EapResult.EapFailure;
44 import com.android.internal.net.eap.EapResult.EapResponse;
45 import com.android.internal.net.eap.EapResult.EapSuccess;
46 import com.android.internal.net.eap.crypto.TlsSession;
47 import com.android.internal.net.eap.crypto.TlsSession.EapTtlsKeyingMaterial;
48 import com.android.internal.net.eap.crypto.TlsSession.TlsResult;
49 import com.android.internal.net.eap.crypto.TlsSessionFactory;
50 import com.android.internal.net.eap.exceptions.EapInvalidRequestException;
51 import com.android.internal.net.eap.exceptions.EapSilentException;
52 import com.android.internal.net.eap.exceptions.ttls.EapTtlsHandshakeException;
53 import com.android.internal.net.eap.exceptions.ttls.EapTtlsParsingException;
54 import com.android.internal.net.eap.message.EapData;
55 import com.android.internal.net.eap.message.EapMessage;
56 import com.android.internal.net.eap.message.ttls.EapTtlsAvp;
57 import com.android.internal.net.eap.message.ttls.EapTtlsAvp.EapTtlsAvpDecoder;
58 import com.android.internal.net.eap.message.ttls.EapTtlsAvp.EapTtlsAvpDecoder.AvpDecodeResult;
59 import com.android.internal.net.eap.message.ttls.EapTtlsInboundFragmentationHelper;
60 import com.android.internal.net.eap.message.ttls.EapTtlsOutboundFragmentationHelper;
61 import com.android.internal.net.eap.message.ttls.EapTtlsOutboundFragmentationHelper.FragmentationResult;
62 import com.android.internal.net.eap.message.ttls.EapTtlsTypeData;
63 import com.android.internal.net.eap.message.ttls.EapTtlsTypeData.EapTtlsAcknowledgement;
64 import com.android.internal.net.eap.message.ttls.EapTtlsTypeData.EapTtlsTypeDataDecoder;
65 import com.android.internal.net.eap.message.ttls.EapTtlsTypeData.EapTtlsTypeDataDecoder.DecodeResult;
66 
67 import java.io.IOException;
68 import java.security.GeneralSecurityException;
69 import java.security.SecureRandom;
70 
71 import javax.net.ssl.SSLException;
72 
73 /**
74  * EapTtlsMethodStateMachine represents the valid paths possible for the EAP-TTLS protocol
75  *
76  * <p>EAP-TTLS sessions will always follow the path:
77  *
78  * <p>Created --+--> Handshake --+--> Tunnel (EAP) --+--> Final
79  *
80  * <p>Note: EAP-TTLS will only be allowed to run once. The inner EAP instance will not be able to
81  * select EAP-TTLS. This is handled in the tunnel state when a new EAP session config is created.
82  *
83  * @see <a href="https://tools.ietf.org/html/rfc5281">RFC 5281, Extensible Authentication Protocol
84  *     Tunneled Transport Layer Security Authenticated Protocol Version 0 (EAP-TTLSv0)</a>
85  */
86 public class EapTtlsMethodStateMachine extends EapMethodStateMachine {
87 
88     @VisibleForTesting public static TlsSessionFactory sTlsSessionFactory = new TlsSessionFactory();
89     private static final int DEFAULT_AVP_VENDOR_ID = 0;
90 
91     private final Context mContext;
92     private final EapTtlsConfig mEapTtlsConfig;
93     private final EapTtlsTypeDataDecoder mTypeDataDecoder;
94     private final SecureRandom mSecureRandom;
95 
96     @VisibleForTesting final EapTtlsInboundFragmentationHelper mInboundFragmentationHelper;
97     @VisibleForTesting final EapTtlsOutboundFragmentationHelper mOutboundFragmentationHelper;
98     @VisibleForTesting TlsSession mTlsSession;
99 
EapTtlsMethodStateMachine( Context context, EapTtlsConfig eapTtlsConfig, SecureRandom secureRandom)100     public EapTtlsMethodStateMachine(
101             Context context,
102             EapTtlsConfig eapTtlsConfig,
103             SecureRandom secureRandom) {
104         this(
105                 context,
106                 eapTtlsConfig,
107                 secureRandom,
108                 new EapTtlsTypeDataDecoder(),
109                 new EapTtlsInboundFragmentationHelper(),
110                 new EapTtlsOutboundFragmentationHelper());
111     }
112 
113     @VisibleForTesting
EapTtlsMethodStateMachine( Context context, EapTtlsConfig eapTtlsConfig, SecureRandom secureRandom, EapTtlsTypeDataDecoder typeDataDecoder, EapTtlsInboundFragmentationHelper inboundFragmentationHelper, EapTtlsOutboundFragmentationHelper outboundFragmentationHelper)114     public EapTtlsMethodStateMachine(
115             Context context,
116             EapTtlsConfig eapTtlsConfig,
117             SecureRandom secureRandom,
118             EapTtlsTypeDataDecoder typeDataDecoder,
119             EapTtlsInboundFragmentationHelper inboundFragmentationHelper,
120             EapTtlsOutboundFragmentationHelper outboundFragmentationHelper) {
121         mContext = context;
122         mEapTtlsConfig = eapTtlsConfig;
123         mTypeDataDecoder = typeDataDecoder;
124         mSecureRandom = secureRandom;
125         mInboundFragmentationHelper = inboundFragmentationHelper;
126         mOutboundFragmentationHelper = outboundFragmentationHelper;
127 
128         transitionTo(new CreatedState());
129     }
130 
131     @Override
132     @EapMethod
getEapMethod()133     int getEapMethod() {
134         return EAP_TYPE_TTLS;
135     }
136 
137     @Override
handleEapNotification(String tag, EapMessage message)138     EapResult handleEapNotification(String tag, EapMessage message) {
139         return EapStateMachine.handleNotification(tag, message);
140     }
141 
142     /**
143      * The created state verifies the start request before transitioning to phase 1 of EAP-TTLS
144      * (RFC5281#7.1)
145      */
146     protected class CreatedState extends EapMethodState {
147         private final String mTAG = this.getClass().getSimpleName();
148 
149         @Override
process(EapMessage message)150         public EapResult process(EapMessage message) {
151             // TODO(b/160781895): Support decoding AVP's pre-tunnel in EAP-TTLS
152             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
153             if (result != null) {
154                 return result;
155             }
156 
157             DecodeResult decodeResult =
158                     mTypeDataDecoder.decodeEapTtlsRequestPacket(message.eapData.eapTypeData);
159             if (!decodeResult.isSuccessfulDecode()) {
160                 LOG.e(mTAG, "Error parsing EAP-TTLS packet type data", decodeResult.eapError.cause);
161                 return decodeResult.eapError;
162             } else if (!decodeResult.eapTypeData.isStart) {
163                 return new EapError(
164                         new EapInvalidRequestException(
165                                 "Unexpected request received in EAP-TTLS: Received first request"
166                                         + " without start bit set."));
167             }
168 
169             return transitionAndProcess(new HandshakeState(), message);
170         }
171     }
172 
173     /**
174      * The handshake (phase 1) state builds the tunnel for tunneled EAP authentication in phase 2
175      *
176      * <p>As per RFC5281#9.2.1, version negotiation occurs during the first exchange between client
177      * and server. In other words, this is an implicit negotiation and is not handled independently.
178      * In this case, the version will always be zero because that is the only currently supported
179      * version of EAP-TTLS at the time of writing. The initiation of the handshake (RFC5281#7.1) is
180      * the first response sent by the client.
181      */
182     protected class HandshakeState extends CloseableTtlsMethodState {
183         private final String mTAG = this.getClass().getSimpleName();
184 
185         private static final int DEFAULT_VENDOR_ID = 0;
186 
187         /**
188          * Processes a message for the handshake state
189          *
190          * <ol>
191          *   <li>Checks for EAP-success, EAP-failure, or EAP notification, returns early if one
192          *       needs to be handled
193          *   <li>Decodes type data, closes the connection if decoding fails
194          *   <li>If outbound data is being fragmented, returns early with the next fragment to be
195          *       sent
196          *   <li>If inbound data is being reassembled, returns early with an ack etc. If nothing has
197          *       returned yet, generates an EAP response for the incoming message
198          *   <li>If this is a start request, and the first message in the handshake state, starts
199          *       the handshake and returns an EAP-Response. Otherwise, processes the incoming
200          *       message in TlsSession, and then sends an EAP-Response.
201          *   <li>If the handshake is complete, sends a tunnelled EAP-Response/Identity and
202          *       transitions to the tunnel state.
203          * </ol>
204          */
205         @Override
process(EapMessage message)206         public EapResult process(EapMessage message) {
207             EapResult eapResult = handleEapSuccessFailureNotification(mTAG, message);
208             if (eapResult != null) {
209                 return eapResult;
210             }
211 
212             DecodeResult decodeResult =
213                     mTypeDataDecoder.decodeEapTtlsRequestPacket(message.eapData.eapTypeData);
214             if (!decodeResult.isSuccessfulDecode()) {
215                 LOG.e(mTAG, "Error parsing EAP-TTLS packet type data", decodeResult.eapError.cause);
216                 if (mTlsSession == null) {
217                     return decodeResult.eapError;
218                 }
219                 return transitionToErroredAndAwaitingClosureState(
220                         mTAG, message.eapIdentifier, decodeResult.eapError);
221             }
222 
223             EapTtlsTypeData eapTtlsRequest = decodeResult.eapTypeData;
224 
225             // If the remote is in the midst of sending a fragmented message, ack the fragment and
226             // return
227             EapResult inboundFragmentAck =
228                     handleInboundFragmentation(mTAG, eapTtlsRequest, message.eapIdentifier);
229             if (inboundFragmentAck != null) {
230                 return inboundFragmentAck;
231             }
232 
233             if (eapTtlsRequest.isStart) {
234                 if (mTlsSession != null) {
235                     return transitionToErroredAndAwaitingClosureState(
236                             mTAG,
237                             message.eapIdentifier,
238                             new EapError(
239                                     new EapInvalidRequestException(
240                                             "Received a start request when a session is already in"
241                                                     + " progress")));
242                 }
243 
244                 return startHandshake(message.eapIdentifier);
245             }
246 
247             EapResult nextOutboundFragment =
248                     getNextOutboundFragment(mTAG, eapTtlsRequest, message.eapIdentifier);
249             if (nextOutboundFragment != null) {
250                 // Skip further processing, send remaining outbound fragments
251                 return nextOutboundFragment;
252             }
253 
254             TlsResult tlsResult;
255 
256             try {
257                 tlsResult =
258                         mTlsSession.processHandshakeData(
259                                 mInboundFragmentationHelper.getAssembledInboundFragment(),
260                                 buildEapIdentityResponseAvp(message.eapIdentifier));
261             } catch (EapSilentException e) {
262                 LOG.e(mTAG, "Error building an identity response.", e);
263                 return transitionToErroredAndAwaitingClosureState(
264                         mTAG, message.eapIdentifier, new EapError(e));
265             }
266 
267             switch (tlsResult.status) {
268                 case TLS_STATUS_TUNNEL_ESTABLISHED:
269                     LOG.d(mTAG, "Tunnel established. Generating a response.");
270                     transitionTo(new TunnelState());
271                     // fallthrough
272                 case TLS_STATUS_SUCCESS:
273                     return buildEapMessageResponse(mTAG, message.eapIdentifier, tlsResult.data);
274                 case TLS_STATUS_CLOSED:
275                     EapError eapError =
276                             new EapError(
277                                     new EapTtlsHandshakeException(
278                                             "Handshake failed to complete and the"
279                                                     + " connection was closed."));
280                     // Because the TLS session is already closed, we only transition to
281                     // ErroredAndAwaitingClosureState as the tls result has data to return from the
282                     // closure
283                     transitionTo(new ErroredAndAwaitingClosureState(eapError));
284                     return buildEapMessageResponse(mTAG, message.eapIdentifier, tlsResult.data);
285                 case TLS_STATUS_FAILURE:
286                     // Handshake failed and attempts to successfully close the tunnel also failed.
287                     // Processing more messages is not possible due to the state of TlsSession so
288                     // transition to FinalState.
289                     transitionTo(new FinalState());
290                     return new EapError(
291                             new EapTtlsHandshakeException(
292                                     "Handshake failed to complete and may not have been closed"
293                                             + " properly."));
294                 default:
295                     return transitionToErroredAndAwaitingClosureState(
296                             mTAG,
297                             message.eapIdentifier,
298                             new EapError(
299                                     new IllegalStateException(
300                                             "Received an unknown TLS result with code "
301                                                     + tlsResult.status)));
302             }
303         }
304 
305         /**
306          * Initializes the TlsSession and starts a TLS handshake
307          *
308          * @param eapIdentifier the eap identifier for the response
309          * @return an EAP response containing the ClientHello message, or an EAP error if the TLS
310          *     handshake fails to begin
311          */
startHandshake(int eapIdentifier)312         private EapResult startHandshake(int eapIdentifier) {
313             try {
314                 mTlsSession =
315                         sTlsSessionFactory.newInstance(
316                                 mEapTtlsConfig.getServerCaCert(), mSecureRandom);
317             } catch (GeneralSecurityException | IOException e) {
318                 return new EapError(
319                         new EapTtlsHandshakeException(
320                                 "There was an error creating the TLS Session.", e));
321             }
322 
323             TlsResult tlsResult = mTlsSession.startHandshake();
324             if (tlsResult.status == TLS_STATUS_FAILURE) {
325                 // Handshake failed and attempts to successfully close the tunnel also failed.
326                 // Processing more messages is not possible due to the state of TlsSession so
327                 // transition to FinalState.
328                 transitionTo(new FinalState());
329                 return new EapError(new EapTtlsHandshakeException("Failed to start handshake."));
330             }
331 
332             return buildEapMessageResponse(mTAG, eapIdentifier, tlsResult.data);
333         }
334 
335         /**
336          * Builds an EAP-MESSAGE AVP containing an EAP-Identity response
337          *
338          * <p>Note that this uses the EAP-Identity in the session config nested within EapTtlsConfig
339          * which may be different than the identity in the top-level EapSessionConfig
340          *
341          * @param eapIdentifier the eap identifier for the response
342          * @throws EapSilentException if an error occurs creating the eap message
343          */
344         @VisibleForTesting
buildEapIdentityResponseAvp(int eapIdentifier)345         byte[] buildEapIdentityResponseAvp(int eapIdentifier) throws EapSilentException {
346             EapData eapData =
347                     new EapData(
348                             EAP_IDENTITY,
349                             mEapTtlsConfig.getInnerEapSessionConfig().getEapIdentity());
350             EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, eapData);
351             return EapTtlsAvp.getEapMessageAvp(DEFAULT_AVP_VENDOR_ID, eapMessage.encode()).encode();
352         }
353 
354         /**
355          * Handles premature EAP-Success and EAP-Failure messages in the handshake state.
356          *
357          * <p>In the case of an EAP-Success or EAP-Failure, the TLS session will be closed but an
358          * EapError or EAP-Failure will be returned. For an invalid type error, the TLS session will
359          * be closed and the state will transition to AwaitingClosure.
360          *
361          * @param message the EapMessage to be checked for early Success/Failure/Notification
362          *     messages
363          * @return the EapResult generated from handling the give EapMessage, or null if the message
364          *     Type matches that of the current EAP method
365          */
366         @Nullable
367         @Override
handleEapSuccessFailure(EapMessage message)368         public EapResult handleEapSuccessFailure(EapMessage message) {
369             if (message.eapCode == EAP_CODE_SUCCESS) {
370                 // EAP-SUCCESS is required to be the last EAP message sent during the EAP protocol,
371                 // so receiving a premature SUCCESS message is an unrecoverable error.
372                 mTlsSession.closeConnection();
373                 return new EapError(
374                         new EapInvalidRequestException(
375                                 "Received an EAP-Success in the handshake state"));
376             } else if (message.eapCode == EAP_CODE_FAILURE) {
377                 mTlsSession.closeConnection();
378                 transitionTo(new FinalState());
379                 return new EapFailure();
380             }
381 
382             return null;
383         }
384     }
385 
386     /**
387      * The tunnel state (phase 2) tunnels data produced by an inner EAP instance
388      *
389      * <p>The tunnel state creates an inner EAP instance via a new EAP state machine and handles
390      * decryption and encryption of data using the previously established TLS tunnel (RFC5281#7.2)
391      */
392     protected class TunnelState extends CloseableTtlsMethodState {
393         private final String mTAG = this.getClass().getSimpleName();
394 
395         @VisibleForTesting EapStateMachine mInnerEapStateMachine;
396         @VisibleForTesting EapTtlsAvpDecoder mEapTtlsAvpDecoder = new EapTtlsAvpDecoder();
397 
TunnelState()398         public TunnelState() {
399             mInnerEapStateMachine =
400                     new EapStateMachine(
401                             mContext, mEapTtlsConfig.getInnerEapSessionConfig(), mSecureRandom);
402         }
403 
404         /**
405          * Processes a message for the inner tunneled authentication method.
406          *
407          * <ol>
408          *   <li>Checks for EAP-success, EAP-failure, or EAP notification, returns early if one
409          *       needs to be handled
410          *   <li>Decodes type data, closes the connection if decoding fails
411          *   <li>If outbound data is being fragmented, returns early with the next fragment to be
412          *       sent
413          *   <li>If inbound data is being reassembled, returns early with an ack etc. If nothing has
414          *       returned yet, generates an EAP response for the incoming message
415          *   <li>Decodes AVP, closes the connection if decoding fails.
416          *   <li>Processes data through inner state machine. Encodes response in AVP, encrypts it
417          *       and sends EAP-Response.
418          * </ol>
419          */
420         @Override
process(EapMessage message)421         public EapResult process(EapMessage message) {
422             EapResult eapResult = handleEapSuccessFailureNotification(mTAG, message);
423             if (eapResult != null) {
424                 return eapResult;
425             }
426 
427             DecodeResult decodeResult =
428                     mTypeDataDecoder.decodeEapTtlsRequestPacket(message.eapData.eapTypeData);
429             if (!decodeResult.isSuccessfulDecode()) {
430                 LOG.e(mTAG, "Error parsing EAP-TTLS packet type data", decodeResult.eapError.cause);
431                 return transitionToErroredAndAwaitingClosureState(
432                         mTAG, message.eapIdentifier, decodeResult.eapError);
433             }
434 
435             EapTtlsTypeData eapTtlsRequest = decodeResult.eapTypeData;
436 
437             EapResult nextOutboundFragment =
438                     getNextOutboundFragment(mTAG, eapTtlsRequest, message.eapIdentifier);
439             if (nextOutboundFragment != null) {
440                 return nextOutboundFragment;
441             }
442 
443             EapResult inboundFragmentAck =
444                     handleInboundFragmentation(mTAG, eapTtlsRequest, message.eapIdentifier);
445             if (inboundFragmentAck != null) {
446                 return inboundFragmentAck;
447             }
448 
449             TlsResult decryptResult =
450                     mTlsSession.processIncomingData(
451                             mInboundFragmentationHelper.getAssembledInboundFragment());
452 
453             EapResult errorResult = handleTunnelTlsResult(decryptResult, message.eapIdentifier);
454             if (errorResult != null) {
455                 return errorResult;
456             }
457 
458             AvpDecodeResult avpDecodeResult = mEapTtlsAvpDecoder.decode(decryptResult.data);
459             if (!avpDecodeResult.isSuccessfulDecode()) {
460                 LOG.e(mTAG, "Error parsing EAP-TTLS AVP", avpDecodeResult.eapError.cause);
461                 return transitionToErroredAndAwaitingClosureState(
462                         mTAG, message.eapIdentifier, avpDecodeResult.eapError);
463             }
464 
465             EapTtlsAvp avp = avpDecodeResult.eapTtlsAvp;
466             LOG.d(
467                     mTAG,
468                     "Incoming AVP has been decrypted and processed. AVP data will be passed to the"
469                             + " inner state machine.");
470 
471             EapResult innerResult = mInnerEapStateMachine.process(avp.data);
472 
473             if (innerResult instanceof EapError) {
474                 return transitionToErroredAndAwaitingClosureState(
475                         mTAG, message.eapIdentifier, (EapError) innerResult);
476             } else if (innerResult instanceof EapFailure) {
477                 LOG.e(mTAG, "Tunneled authentication failed");
478                 mTlsSession.closeConnection();
479                 transitionTo(new FinalState());
480                 return innerResult;
481             } else if (innerResult instanceof EapSuccess) {
482                 Exception invalidSuccess =
483                         new EapInvalidRequestException(
484                                 "Received an unexpected EapSuccess from the inner state machine.");
485                 transitionToErroredAndAwaitingClosureState(
486                         mTAG, message.eapIdentifier, new EapError(invalidSuccess));
487             }
488 
489             LOG.d(mTAG, "Received EapResponse from innerStateMachine");
490             TlsResult encryptResult;
491 
492             EapResponse innerResponse = (EapResponse) innerResult;
493             EapTtlsAvp outgoingAvp =
494                     EapTtlsAvp.getEapMessageAvp(DEFAULT_AVP_VENDOR_ID, innerResponse.packet);
495             encryptResult = mTlsSession.processOutgoingData(outgoingAvp.encode());
496 
497             errorResult = handleTunnelTlsResult(encryptResult, message.eapIdentifier);
498             if (errorResult != null) {
499                 return errorResult;
500             }
501 
502             LOG.d(mTAG, "Outbound AVP has been assembled and encrypted. Building EAP Response.");
503 
504             return buildEapMessageResponse(mTAG, message.eapIdentifier, encryptResult.data);
505         }
506 
507         /**
508          * Validates the results of an encryption or decryption operation
509          *
510          * <p>If the result is an error state, the tunnel will be closed and a response or EapError
511          * will be returned. Otherwise, null is returned to indicate that processing can continue.
512          *
513          * @param result a TlsResult encapsulating the results of an encrypt or decrypt operation
514          * @param eapIdentifier the eap identifier from the latest message
515          * @return an eap response if an error occurs or null if processing can continue
516          */
517         @Nullable
handleTunnelTlsResult(TlsResult result, int eapIdentifier)518         EapResult handleTunnelTlsResult(TlsResult result, int eapIdentifier) {
519             switch (result.status) {
520                 case TLS_STATUS_SUCCESS:
521                     return null;
522                 case TLS_STATUS_CLOSED:
523                     Exception closeException =
524                             new SSLException(
525                                     "TLS Session failed to encrypt or decrypt data"
526                                             + " and was closed.");
527                     // Because the TLS session is already closed, we only transition to
528                     // ErroredAndAwaitingClosureState as the tls result has data to return from the
529                     // closure
530                     transitionTo(new ErroredAndAwaitingClosureState(new EapError(closeException)));
531                     return buildEapMessageResponse(mTAG, eapIdentifier, result.data);
532                 case TLS_STATUS_FAILURE:
533                     transitionTo(new FinalState());
534                     return new EapError(
535                             new SSLException(
536                                     "Failed to encrypt or decrypt message. Tunnel could not be"
537                                             + " closed properly"));
538                 default:
539                     Exception illegalStateException =
540                             new IllegalStateException(
541                                     "Received an unexpected TLS result with code " + result.status);
542                     return transitionToErroredAndAwaitingClosureState(
543                             mTAG, eapIdentifier, new EapError(illegalStateException));
544             }
545         }
546 
547         /**
548          * Handles EAP-Success and EAP-Failure messages in the tunnel state
549          *
550          * <p>Both success/failure messages are passed into the inner state machine for processing.
551          *
552          * <p>If an EAP-Success is returned by the inner state machine, it is discarded and a new
553          * EAP-Success that contains the keying material generated during the TLS negotiation is
554          * sent instead.
555          *
556          * @param message the EapMessage to be checked for Success/Failure
557          * @return the EapResult generated from handling the give EapMessage, or null if the message
558          *     Type matches that of the current EAP method
559          */
560         @Nullable
561         @Override
handleEapSuccessFailure(EapMessage message)562         EapResult handleEapSuccessFailure(EapMessage message) {
563             if (message.eapCode == EAP_CODE_SUCCESS || message.eapCode == EAP_CODE_FAILURE) {
564                 EapResult innerResult = mInnerEapStateMachine.process(message.encode());
565                 if (innerResult instanceof EapSuccess) {
566                     EapTtlsKeyingMaterial keyingMaterial = mTlsSession.generateKeyingMaterial();
567                     mTlsSession.closeConnection();
568                     transitionTo(new FinalState());
569 
570                     if (!keyingMaterial.isSuccessful()) {
571                         return keyingMaterial.eapError;
572                     }
573 
574                     return new EapSuccess(keyingMaterial.msk, keyingMaterial.emsk);
575                 }
576 
577                 transitionTo(new FinalState());
578                 mTlsSession.closeConnection();
579                 return innerResult;
580             }
581 
582             return null;
583         }
584     }
585 
586     /**
587      * The closure state handles closure of the TLS session in EAP-TTLS
588      *
589      * <p>Note that this state is only entered following an error. If EAP authentication completes
590      * successfully or fails, the tunnel is assumed to have implicitly closed.
591      */
592     protected class ErroredAndAwaitingClosureState extends EapMethodState {
593         private final String mTAG = this.getClass().getSimpleName();
594 
595         private final EapError mEapError;
596 
597         /**
598          * Initializes the closure state
599          *
600          * <p>The errored and awaiting closure state is an error state. If a server responds to a
601          * close-notify, the data is processed and the EAP error which encapsulates the initial
602          * error that caused the closure is returned
603          *
604          * @param eapError an EAP error that contains the error that initially caused a close to
605          *     occur
606          */
ErroredAndAwaitingClosureState(EapError eapError)607         public ErroredAndAwaitingClosureState(EapError eapError) {
608             mEapError = eapError;
609         }
610 
611         @Override
process(EapMessage message)612         public EapResult process(EapMessage message) {
613             EapResult result = handleEapSuccessFailureNotification(mTAG, message);
614             if (result != null) {
615                 return result;
616             }
617 
618             DecodeResult decodeResult =
619                     mTypeDataDecoder.decodeEapTtlsRequestPacket(message.eapData.eapTypeData);
620             if (!decodeResult.isSuccessfulDecode()) {
621                 LOG.e(mTAG, "Error parsing EAP-TTLS packet type data", decodeResult.eapError.cause);
622                 return decodeResult.eapError;
623             }
624 
625             // if the server sent data, we process it and return an EapError.
626             // A response is not required and is additionally unlikely as we have already sent the
627             // closure-notify
628             mTlsSession.processIncomingData(decodeResult.eapTypeData.data);
629 
630             return mEapError;
631         }
632     }
633 
634     /**
635      * Transitions to the ErroredAndAwaitingClosureState and attempts to close the TLS tunnel
636      *
637      * @param tag the tag of the calling class
638      * @param eapIdentifier the EAP identifier from the most recent EAP request
639      * @param eapError the EAP error to return if closure fails
640      * @return a closure notify TLS message or an EAP error if one cannot be generated
641      */
642     @VisibleForTesting
transitionToErroredAndAwaitingClosureState( String tag, int eapIdentifier, EapError eapError)643     EapResult transitionToErroredAndAwaitingClosureState(
644             String tag, int eapIdentifier, EapError eapError) {
645         TlsResult closureResult = mTlsSession.closeConnection();
646         if (closureResult.status != TLS_STATUS_CLOSED) {
647             LOG.e(tag, "Failed to close the TLS session");
648             transitionTo(new FinalState());
649             return eapError;
650         }
651 
652         transitionTo(new ErroredAndAwaitingClosureState(eapError));
653         return buildEapMessageResponse(
654                 tag,
655                 eapIdentifier,
656                 EapTtlsTypeData.getEapTtlsTypeData(
657                         false /* isFragmented */,
658                         false /* start */,
659                         0 /* version 0 */,
660                         closureResult.data.length,
661                         closureResult.data));
662     }
663 
664     /**
665      * Verifies whether outbound fragmentation is in progress and constructs the next fragment if
666      * necessary
667      *
668      * @param tag the tag for the calling class
669      * @param eapTtlsRequest the request received from the server
670      * @param eapIdentifier the eap identifier from the latest message
671      * @return an eap response if the next fragment exists, or null if no fragmentation is in
672      *     progress
673      */
674     @Nullable
getNextOutboundFragment( String tag, EapTtlsTypeData eapTtlsRequest, int eapIdentifier)675     private EapResult getNextOutboundFragment(
676             String tag, EapTtlsTypeData eapTtlsRequest, int eapIdentifier) {
677         if (eapTtlsRequest.isAcknowledgmentPacket()) {
678             if (mOutboundFragmentationHelper.hasRemainingFragments()) {
679                 FragmentationResult result = mOutboundFragmentationHelper.getNextOutboundFragment();
680                 return buildEapMessageResponse(
681                         tag,
682                         eapIdentifier,
683                         EapTtlsTypeData.getEapTtlsTypeData(
684                                 result.hasRemainingFragments,
685                                 false /* start */,
686                                 0 /* version 0 */,
687                                 0 /* messageLength */,
688                                 result.fragmentedData));
689             } else {
690                 return transitionToErroredAndAwaitingClosureState(
691                         tag,
692                         eapIdentifier,
693                         new EapError(
694                                 new EapInvalidRequestException(
695                                         "Received an ack but no packet was in the process of"
696                                                 + " being fragmented.")));
697             }
698         } else if (mOutboundFragmentationHelper.hasRemainingFragments()) {
699             return transitionToErroredAndAwaitingClosureState(
700                     tag,
701                     eapIdentifier,
702                     new EapError(
703                             new EapInvalidRequestException(
704                                     "Received a standard EAP-Request but was expecting an ack to a"
705                                             + " fragment.")));
706         }
707 
708         return null;
709     }
710 
711     /**
712      * Processes incoming data, and if necessary, assembles fragments
713      *
714      * @param tag the tag for the calling class
715      * @param eapTtlsRequest the request received from the server
716      * @param eapIdentifier the eap identifier from the latest message
717      * @return an acknowledgment if the received data is a fragment, null if data is ready to
718      *     process
719      */
720     @Nullable
handleInboundFragmentation( String tag, EapTtlsTypeData eapTtlsRequest, int eapIdentifier)721     private EapResult handleInboundFragmentation(
722             String tag, EapTtlsTypeData eapTtlsRequest, int eapIdentifier) {
723         int fragmentationStatus =
724                 mInboundFragmentationHelper.assembleInboundMessage(eapTtlsRequest);
725 
726         switch (fragmentationStatus) {
727             case FRAGMENTATION_STATUS_ASSEMBLED:
728                 return null;
729             case FRAGMENTATION_STATUS_ACK:
730                 LOG.d(tag, "Packet is fragmented. Generating an acknowledgement response.");
731                 return buildEapMessageResponse(
732                         tag, eapIdentifier, EapTtlsAcknowledgement.getEapTtlsAcknowledgement());
733             case FRAGMENTATION_STATUS_INVALID:
734                 return transitionToErroredAndAwaitingClosureState(
735                         tag,
736                         eapIdentifier,
737                         new EapError(
738                                 new EapTtlsParsingException(
739                                         "Fragmentation failure: There was an error decoding the"
740                                                 + " fragmented request.")));
741             default:
742                 return transitionToErroredAndAwaitingClosureState(
743                         tag,
744                         eapIdentifier,
745                         new EapError(
746                                 new IllegalStateException(
747                                         "Received an unknown fragmentation status when assembling"
748                                                 + " an inbound fragment: "
749                                                 + fragmentationStatus)));
750         }
751     }
752 
753     /**
754      * Takes outbound data and assembles an EAP-Response.
755      *
756      * <p>The data will be fragmented if necessary
757      *
758      * @param tag the tag of the calling class
759      * @param eapIdentifier the EAP identifier from the most recent EAP request
760      * @param data the data used to build the EAP-TTLS type data
761      * @return an EAP result that is either an EAP response or an EAP error
762      */
buildEapMessageResponse(String tag, int eapIdentifier, byte[] data)763     private EapResult buildEapMessageResponse(String tag, int eapIdentifier, byte[] data) {
764         // TODO(b/165668196): Modify outbound fragmentation helper to be per-message in EAP-TTLS
765         mOutboundFragmentationHelper.setupOutboundFragmentation(data);
766         FragmentationResult result = mOutboundFragmentationHelper.getNextOutboundFragment();
767 
768         // As per RFC5281#9.2.2, an unfragmented packet may have the length bit set
769         return buildEapMessageResponse(
770                 tag,
771                 eapIdentifier,
772                 EapTtlsTypeData.getEapTtlsTypeData(
773                         result.hasRemainingFragments,
774                         false /* start */,
775                         0 /* version 0 */,
776                         data.length,
777                         result.fragmentedData));
778     }
779 
780     /**
781      * Takes an already constructed EapTtlsTypeData and builds an EAP-Response
782      *
783      * @param tag the tag of the calling class
784      * @param eapIdentifier the EAP identifier from the most recent EAP request
785      * @param eapTtlsTypeData the type data to use in the EAP Response
786      * @return an EAP result that is either an EAP response or an EAP error
787      */
buildEapMessageResponse( String tag, int eapIdentifier, EapTtlsTypeData eapTtlsTypeData)788     private EapResult buildEapMessageResponse(
789             String tag, int eapIdentifier, EapTtlsTypeData eapTtlsTypeData) {
790         try {
791             EapData eapData = new EapData(getEapMethod(), eapTtlsTypeData.encode());
792             EapMessage eapMessage = new EapMessage(EAP_CODE_RESPONSE, eapIdentifier, eapData);
793             return EapResponse.getEapResponse(eapMessage);
794         } catch (EapSilentException ex) {
795             LOG.e(tag, "Error building response EapMessage", ex);
796             return new EapError(ex);
797         }
798     }
799 
800     /**
801      * CloseableTtlsMethodState defines specific behaviour for handling EAP-Messages in EAP-TTLS
802      *
803      * <p>EAP-TTLS requires specific handling compared to what is defined in {@link EapMethodState}
804      * as the tunnel needs to be closed. Furthermore, EAP-Success/EAP-Failure handling differs in
805      * the tunnel state as it needs to be processed by the inner authentication method.
806      *
807      * <p>
808      */
809     abstract class CloseableTtlsMethodState extends EapMethodState {
handleEapSuccessFailure(EapMessage message)810         abstract EapResult handleEapSuccessFailure(EapMessage message);
811 
812         @Override
813         @Nullable
handleEapSuccessFailureNotification(String tag, EapMessage message)814         EapResult handleEapSuccessFailureNotification(String tag, EapMessage message) {
815             EapResult eapResult = handleEapSuccessFailure(message);
816             if (eapResult != null) {
817                 return eapResult;
818             }
819 
820             if (message.eapData.eapType == EAP_NOTIFICATION) {
821                 return handleEapNotification(tag, message);
822             } else if (message.eapData.eapType != EAP_TYPE_TTLS) {
823                 EapError eapError =
824                         new EapError(
825                                 new EapInvalidRequestException(
826                                         "Expected EAP Type "
827                                                 + getEapMethod()
828                                                 + ", received "
829                                                 + message.eapData.eapType));
830                 return transitionToErroredAndAwaitingClosureState(
831                         tag, message.eapIdentifier, eapError);
832             }
833 
834             return null;
835         }
836     }
837 }
838 
839