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