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 static final EapRandomFactory DEFAULT_RANDOM_FACTORY = 52 () -> { 53 return new SecureRandom(); 54 }; 55 private final Executor mWorkerPool; 56 private final EapStateMachine mStateMachine; 57 private final IEapCallback mCb; 58 private final long mTimeoutMillis; 59 private boolean mCallbackFired = false; 60 61 /** 62 * Constructor for EapAuthenticator 63 * 64 * @param looper Looper for running a message loop 65 * @param cb IEapCallback for callbacks to the client 66 * @param context Context for this EapAuthenticator 67 * @param eapSessionConfig Configuration for an EapAuthenticator 68 */ EapAuthenticator( Looper looper, IEapCallback cb, Context context, EapSessionConfig eapSessionConfig)69 public EapAuthenticator( 70 Looper looper, IEapCallback cb, Context context, EapSessionConfig eapSessionConfig) { 71 this(looper, cb, context, eapSessionConfig, DEFAULT_RANDOM_FACTORY); 72 } 73 74 /** 75 * Test-Only Constructor for EapAuthenticator. 76 * 77 * @param looper Looper for running a message loop 78 * @param cb IEapCallback for callbacks to the client 79 * @param context Context for this EapAuthenticator 80 * @param eapSessionConfig Configuration for an EapAuthenticator 81 * @param randomnessFactory the randomness factory 82 * @hide 83 */ EapAuthenticator( Looper looper, IEapCallback cb, Context context, EapSessionConfig eapSessionConfig, EapRandomFactory randomnessFactory)84 public EapAuthenticator( 85 Looper looper, 86 IEapCallback cb, 87 Context context, 88 EapSessionConfig eapSessionConfig, 89 EapRandomFactory randomnessFactory) { 90 this( 91 looper, 92 cb, 93 new EapStateMachine( 94 context, 95 eapSessionConfig, 96 createNewRandomIfNull(randomnessFactory.getRandom())), 97 Executors.newSingleThreadExecutor(), 98 DEFAULT_TIMEOUT_MILLIS); 99 } 100 101 @VisibleForTesting EapAuthenticator( Looper looper, IEapCallback cb, EapStateMachine eapStateMachine, Executor executor, long timeoutMillis)102 EapAuthenticator( 103 Looper looper, 104 IEapCallback cb, 105 EapStateMachine eapStateMachine, 106 Executor executor, 107 long timeoutMillis) { 108 super(looper); 109 110 mCb = cb; 111 mStateMachine = eapStateMachine; 112 mWorkerPool = executor; 113 mTimeoutMillis = timeoutMillis; 114 } 115 createNewRandomIfNull(SecureRandom random)116 private static SecureRandom createNewRandomIfNull(SecureRandom random) { 117 return random == null ? new SecureRandom() : random; 118 } 119 120 /** SecureRandom factory for EAP */ 121 public interface EapRandomFactory { 122 /** Returns a SecureRandom instance */ getRandom()123 SecureRandom getRandom(); 124 } 125 126 @Override handleMessage(Message msg)127 public void handleMessage(Message msg) { 128 // No messages processed here. Only runnables. Drop all messages. 129 } 130 131 /** 132 * Processes the given msgBytes within the context of the current EAP Session. 133 * 134 * <p>If the given message is successfully processed, the relevant {@link IEapCallback} function 135 * is used. Otherwise, {@link IEapCallback#onError(Throwable)} is called. 136 * 137 * @param msgBytes the byte-array encoded EAP message to be processed 138 */ processEapMessage(byte[] msgBytes)139 public void processEapMessage(byte[] msgBytes) { 140 // reset 141 mCallbackFired = false; 142 143 postDelayed( 144 () -> { 145 if (!mCallbackFired) { 146 // Fire failed callback 147 mCallbackFired = true; 148 LOG.e(TAG, "Timeout occurred in EapStateMachine"); 149 mCb.onError(new TimeoutException("Timeout while processing message")); 150 } 151 }, 152 EapAuthenticator.this, 153 mTimeoutMillis); 154 155 // proxy to worker thread for async processing 156 mWorkerPool.execute( 157 () -> { 158 // Any unhandled exceptions within the state machine are caught here to make 159 // sure that the caller does not wait for the full timeout duration before being 160 // notified of a failure. 161 EapResult processResponse; 162 try { 163 processResponse = mStateMachine.process(msgBytes); 164 } catch (Exception ex) { 165 LOG.e(TAG, "Exception thrown while processing message", ex); 166 processResponse = new EapError(ex); 167 } 168 169 final EapResult finalProcessResponse = processResponse; 170 EapAuthenticator.this.post( 171 () -> { 172 // No synchronization needed, since Handler serializes 173 if (!mCallbackFired) { 174 LOG.i( 175 TAG, 176 "EapStateMachine returned " 177 + finalProcessResponse 178 .getClass() 179 .getSimpleName()); 180 181 if (finalProcessResponse instanceof EapResponse) { 182 mCb.onResponse(((EapResponse) finalProcessResponse).packet); 183 } else if (finalProcessResponse instanceof EapError) { 184 EapError eapError = (EapError) finalProcessResponse; 185 LOG.e( 186 TAG, 187 "EapError returned with cause=" + eapError.cause); 188 mCb.onError(eapError.cause); 189 } else if (finalProcessResponse instanceof EapSuccess) { 190 EapSuccess eapSuccess = (EapSuccess) finalProcessResponse; 191 LOG.d( 192 TAG, 193 "EapSuccess with" 194 + " MSK=" + LOG.pii(eapSuccess.msk) 195 + " EMSK=" + LOG.pii(eapSuccess.emsk)); 196 mCb.onSuccess(eapSuccess.msk, eapSuccess.emsk); 197 } else { // finalProcessResponse instanceof EapFailure 198 mCb.onFail(); 199 } 200 201 mCallbackFired = true; 202 203 // Ensure delayed timeout runnable does not fire 204 EapAuthenticator.this.removeCallbacksAndMessages( 205 EapAuthenticator.this); 206 } 207 }); 208 }); 209 } 210 } 211