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; 18 19 import android.content.Context; 20 import android.net.eap.EapSessionConfig; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.net.eap.EapResult.EapError; 27 import com.android.internal.net.eap.EapResult.EapResponse; 28 import com.android.internal.net.eap.EapResult.EapSuccess; 29 import com.android.internal.net.eap.statemachine.EapStateMachine; 30 import com.android.internal.net.utils.Log; 31 32 import java.security.SecureRandom; 33 import java.util.concurrent.Executor; 34 import java.util.concurrent.Executors; 35 import java.util.concurrent.TimeoutException; 36 37 /** 38 * EapAuthenticator represents an EAP peer implementation. 39 * 40 * @see <a href="https://tools.ietf.org/html/rfc3748#section-4">RFC 3748, Extensible Authentication 41 * Protocol (EAP)</a> 42 */ 43 public class EapAuthenticator extends Handler { 44 private static final String EAP_TAG = "EAP"; 45 private static final boolean LOG_SENSITIVE = false; 46 public static final Log LOG = new Log(EAP_TAG, LOG_SENSITIVE); 47 48 private static final String TAG = EapAuthenticator.class.getSimpleName(); 49 private static final long DEFAULT_TIMEOUT_MILLIS = 7000L; 50 51 private final Executor mWorkerPool; 52 private final EapStateMachine mStateMachine; 53 private final IEapCallback mCb; 54 private final long mTimeoutMillis; 55 private boolean mCallbackFired = false; 56 57 /** 58 * Constructor for EapAuthenticator. 59 * 60 * @param eapContext the context of this EapAuthenticator 61 * @param cb IEapCallback for callbacks to the client 62 * @param eapSessionConfig Configuration for an EapAuthenticator 63 * @hide 64 */ EapAuthenticator( EapContext eapContext, IEapCallback cb, EapSessionConfig eapSessionConfig)65 public EapAuthenticator( 66 EapContext eapContext, IEapCallback cb, EapSessionConfig eapSessionConfig) { 67 this( 68 eapContext.getLooper(), 69 cb, 70 new EapStateMachine( 71 eapContext.getContext(), 72 eapSessionConfig, 73 createNewRandomIfNull(eapContext.getRandomnessFactory().getRandom())), 74 Executors.newSingleThreadExecutor(), 75 DEFAULT_TIMEOUT_MILLIS); 76 } 77 78 @VisibleForTesting EapAuthenticator( Looper looper, IEapCallback cb, EapStateMachine eapStateMachine, Executor executor, long timeoutMillis)79 EapAuthenticator( 80 Looper looper, 81 IEapCallback cb, 82 EapStateMachine eapStateMachine, 83 Executor executor, 84 long timeoutMillis) { 85 super(looper); 86 87 mCb = cb; 88 mStateMachine = eapStateMachine; 89 mWorkerPool = executor; 90 mTimeoutMillis = timeoutMillis; 91 } 92 createNewRandomIfNull(SecureRandom random)93 private static SecureRandom createNewRandomIfNull(SecureRandom random) { 94 return random == null ? new SecureRandom() : random; 95 } 96 97 /** SecureRandom factory for EAP */ 98 public interface EapRandomFactory { 99 /** Returns a SecureRandom instance */ getRandom()100 SecureRandom getRandom(); 101 } 102 103 /** EapContext provides interface to retrieve context information for this EapAuthenticator */ 104 public interface EapContext { 105 /** Gets the Looper */ getLooper()106 Looper getLooper(); 107 /** Gets the Context */ getContext()108 Context getContext(); 109 /** Gets the EapRandomFactory which will control if the EapAuthenticator is in test mode */ getRandomnessFactory()110 EapRandomFactory getRandomnessFactory(); 111 } 112 113 @Override handleMessage(Message msg)114 public void handleMessage(Message msg) { 115 // No messages processed here. Only runnables. Drop all messages. 116 } 117 118 /** 119 * Processes the given msgBytes within the context of the current EAP Session. 120 * 121 * <p>If the given message is successfully processed, the relevant {@link IEapCallback} function 122 * is used. Otherwise, {@link IEapCallback#onError(Throwable)} is called. 123 * 124 * @param msgBytes the byte-array encoded EAP message to be processed 125 */ processEapMessage(byte[] msgBytes)126 public void processEapMessage(byte[] msgBytes) { 127 // reset 128 mCallbackFired = false; 129 130 postDelayed( 131 () -> { 132 if (!mCallbackFired) { 133 // Fire failed callback 134 mCallbackFired = true; 135 LOG.e(TAG, "Timeout occurred in EapStateMachine"); 136 mCb.onError(new TimeoutException("Timeout while processing message")); 137 } 138 }, 139 EapAuthenticator.this, 140 mTimeoutMillis); 141 142 // proxy to worker thread for async processing 143 mWorkerPool.execute( 144 () -> { 145 // Any unhandled exceptions within the state machine are caught here to make 146 // sure that the caller does not wait for the full timeout duration before being 147 // notified of a failure. 148 EapResult processResponse; 149 try { 150 processResponse = mStateMachine.process(msgBytes); 151 } catch (Exception ex) { 152 LOG.e(TAG, "Exception thrown while processing message", ex); 153 processResponse = new EapError(ex); 154 } 155 156 final EapResult finalProcessResponse = processResponse; 157 EapAuthenticator.this.post( 158 () -> { 159 // No synchronization needed, since Handler serializes 160 if (!mCallbackFired) { 161 LOG.i( 162 TAG, 163 "EapStateMachine returned " 164 + finalProcessResponse 165 .getClass() 166 .getSimpleName()); 167 168 if (finalProcessResponse instanceof EapResponse) { 169 mCb.onResponse( 170 ((EapResponse) finalProcessResponse).packet, 171 ((EapResponse) finalProcessResponse).flagMask); 172 } else if (finalProcessResponse instanceof EapError) { 173 EapError eapError = (EapError) finalProcessResponse; 174 LOG.e( 175 TAG, 176 "EapError returned with cause=" + eapError.cause); 177 mCb.onError(eapError.cause); 178 } else if (finalProcessResponse instanceof EapSuccess) { 179 EapSuccess eapSuccess = (EapSuccess) finalProcessResponse; 180 LOG.d( 181 TAG, 182 "EapSuccess with" 183 + " MSK=" 184 + LOG.pii(eapSuccess.msk) 185 + " EMSK=" 186 + LOG.pii(eapSuccess.emsk)); 187 mCb.onSuccess( 188 eapSuccess.msk, 189 eapSuccess.emsk, 190 eapSuccess.getEapInfo()); 191 } else { // finalProcessResponse instanceof EapFailure 192 mCb.onFail(); 193 } 194 195 mCallbackFired = true; 196 197 // Ensure delayed timeout runnable does not fire 198 EapAuthenticator.this.removeCallbacksAndMessages( 199 EapAuthenticator.this); 200 } 201 }); 202 }); 203 } 204 } 205