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.msk));
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