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