1 /*
2  * Copyright (C) 2022 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.server.credentials;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.credentials.ClearCredentialStateException;
25 import android.credentials.CreateCredentialException;
26 import android.credentials.GetCredentialException;
27 import android.os.Binder;
28 import android.os.Handler;
29 import android.os.ICancellationSignal;
30 import android.os.RemoteException;
31 import android.service.credentials.BeginCreateCredentialRequest;
32 import android.service.credentials.BeginCreateCredentialResponse;
33 import android.service.credentials.BeginGetCredentialRequest;
34 import android.service.credentials.BeginGetCredentialResponse;
35 import android.service.credentials.ClearCredentialStateRequest;
36 import android.service.credentials.CredentialProviderErrors;
37 import android.service.credentials.CredentialProviderService;
38 import android.service.credentials.IBeginCreateCredentialCallback;
39 import android.service.credentials.IBeginGetCredentialCallback;
40 import android.service.credentials.IClearCredentialStateCallback;
41 import android.service.credentials.ICredentialProviderService;
42 import android.text.format.DateUtils;
43 import android.util.Slog;
44 
45 import com.android.internal.infra.ServiceConnector;
46 
47 import java.util.concurrent.CancellationException;
48 import java.util.concurrent.CompletableFuture;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.TimeoutException;
51 import java.util.concurrent.atomic.AtomicBoolean;
52 import java.util.concurrent.atomic.AtomicReference;
53 
54 /**
55  * Handles connections with the remote credential provider
56  *
57  * @hide
58  */
59 public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService> {
60 
61     private static final String TAG = "RemoteCredentialService";
62     /** Timeout for a single request. */
63     private static final long TIMEOUT_REQUEST_MILLIS = 3 * DateUtils.SECOND_IN_MILLIS;
64     /** Timeout to unbind after the task queue is empty. */
65     private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
66             5 * DateUtils.SECOND_IN_MILLIS;
67 
68     private final ComponentName mComponentName;
69 
70     private AtomicBoolean mOngoingRequest = new AtomicBoolean(false);
71 
72     @Nullable private ProviderCallbacks mCallback;
73 
74     /**
75      * Callbacks to be invoked when the provider remote service responds with a
76      * success or failure.
77      *
78      * @param <T> the type of response expected from the provider
79      */
80     public interface ProviderCallbacks<T> {
81         /** Called when a successful response is received from the remote provider. */
onProviderResponseSuccess(@ullable T response)82         void onProviderResponseSuccess(@Nullable T response);
83 
84         /** Called when a failure response is received from the remote provider. */
onProviderResponseFailure(int internalErrorCode, @Nullable Exception e)85         void onProviderResponseFailure(int internalErrorCode, @Nullable Exception e);
86 
87         /** Called when the remote provider service dies. */
onProviderServiceDied(RemoteCredentialService service)88         void onProviderServiceDied(RemoteCredentialService service);
89 
90         /** Called to set the cancellation transport from the remote provider service. */
onProviderCancellable(ICancellationSignal cancellation)91         void onProviderCancellable(ICancellationSignal cancellation);
92     }
93 
RemoteCredentialService(@onNull Context context, @NonNull ComponentName componentName, int userId)94     public RemoteCredentialService(@NonNull Context context,
95             @NonNull ComponentName componentName, int userId) {
96         super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
97                         .setComponent(componentName), /*bindingFlags=*/0,
98                 userId, ICredentialProviderService.Stub::asInterface);
99         mComponentName = componentName;
100     }
101 
setCallback(ProviderCallbacks callback)102     public void setCallback(ProviderCallbacks callback) {
103         mCallback = callback;
104     }
105 
106     /** Unbinds automatically after this amount of time. */
107     @Override
getAutoDisconnectTimeoutMs()108     protected long getAutoDisconnectTimeoutMs() {
109         return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
110     }
111 
112     @Override
onBindingDied(ComponentName name)113     public void onBindingDied(ComponentName name) {
114         super.onBindingDied(name);
115 
116         Slog.w(TAG, "binding died for: " + name);
117     }
118 
119     @Override
binderDied()120     public void binderDied() {
121         super.binderDied();
122         Slog.w(TAG, "binderDied");
123 
124         if (mCallback != null) {
125             mOngoingRequest.set(false);
126             mCallback.onProviderServiceDied(this);
127         }
128 
129     }
130 
131     /** Return the componentName of the service to be connected. */
132     @NonNull
getComponentName()133     public ComponentName getComponentName() {
134         return mComponentName;
135     }
136 
137     /** Destroys this remote service by unbinding the connection. */
destroy()138     public void destroy() {
139         unbind();
140     }
141 
142     /**
143      * Main entry point to be called for executing a getCredential call on the remote
144      * provider service.
145      *
146      * @param request  the request to be sent to the provider
147      */
onBeginGetCredential(@onNull BeginGetCredentialRequest request)148     public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request) {
149         if (mCallback == null) {
150             Slog.w(TAG, "Callback is not set");
151             return;
152         }
153         mOngoingRequest.set(true);
154 
155         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
156         AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
157                 new AtomicReference<>();
158 
159 
160         CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> {
161             CompletableFuture<BeginGetCredentialResponse> getCredentials =
162                     new CompletableFuture<>();
163             final long originalCallingUidToken = Binder.clearCallingIdentity();
164             try {
165                 service.onBeginGetCredential(request,
166                         new IBeginGetCredentialCallback.Stub() {
167                             @Override
168                             public void onSuccess(BeginGetCredentialResponse response) {
169                                 getCredentials.complete(response);
170                             }
171 
172                             @Override
173                             public void onFailure(String errorType, CharSequence message) {
174                                 String errorMsg = message == null ? "" : String.valueOf(
175                                         message);
176                                 getCredentials.completeExceptionally(
177                                         new GetCredentialException(errorType, errorMsg));
178                             }
179 
180                             @Override
181                             public void onCancellable(ICancellationSignal cancellation) {
182                                 CompletableFuture<BeginGetCredentialResponse> future =
183                                         futureRef.get();
184                                 if (future != null && future.isCancelled()) {
185                                     dispatchCancellationSignal(cancellation);
186                                 } else {
187                                     cancellationSink.set(cancellation);
188                                     if (mCallback != null) {
189                                         mCallback.onProviderCancellable(cancellation);
190                                     }
191                                 }
192                             }
193                         });
194                 return getCredentials;
195             } finally {
196                 Binder.restoreCallingIdentity(originalCallingUidToken);
197             }
198         }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
199         futureRef.set(connectThenExecute);
200 
201         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
202                 handleExecutionResponse(result, error, cancellationSink)));
203     }
204 
205     /**
206      * Main entry point to be called for executing a beginCreateCredential call on the remote
207      * provider service.
208      *
209      * @param request  the request to be sent to the provider
210      */
onBeginCreateCredential(@onNull BeginCreateCredentialRequest request)211     public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request) {
212         if (mCallback == null) {
213             Slog.w(TAG, "Callback is not set");
214             return;
215         }
216         mOngoingRequest.set(true);
217 
218         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
219         AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
220                 new AtomicReference<>();
221 
222         CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
223                 postAsync(service -> {
224                     CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
225                             new CompletableFuture<>();
226                     final long originalCallingUidToken = Binder.clearCallingIdentity();
227                     try {
228                         service.onBeginCreateCredential(
229                                 request, new IBeginCreateCredentialCallback.Stub() {
230                                     @Override
231                                     public void onSuccess(BeginCreateCredentialResponse response) {
232                                         createCredentialFuture.complete(response);
233                                     }
234 
235                                     @Override
236                                     public void onFailure(String errorType, CharSequence message) {
237                                         String errorMsg = message == null ? "" : String.valueOf(
238                                                 message);
239                                         createCredentialFuture.completeExceptionally(
240                                                 new CreateCredentialException(errorType, errorMsg));
241                                     }
242 
243                                     @Override
244                                     public void onCancellable(ICancellationSignal cancellation) {
245                                         CompletableFuture<BeginCreateCredentialResponse> future =
246                                                 futureRef.get();
247                                         if (future != null && future.isCancelled()) {
248                                             dispatchCancellationSignal(cancellation);
249                                         } else {
250                                             cancellationSink.set(cancellation);
251                                             if (mCallback != null) {
252                                                 mCallback.onProviderCancellable(cancellation);
253                                             }
254                                         }
255                                     }
256                                 });
257                         return createCredentialFuture;
258                     } finally {
259                         Binder.restoreCallingIdentity(originalCallingUidToken);
260                     }
261                 }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
262         futureRef.set(connectThenExecute);
263 
264         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
265                 handleExecutionResponse(result, error, cancellationSink)));
266     }
267 
268     /**
269      * Main entry point to be called for executing a clearCredentialState call on the remote
270      * provider service.
271      *
272      * @param request  the request to be sent to the provider
273      */
onClearCredentialState(@onNull ClearCredentialStateRequest request)274     public void onClearCredentialState(@NonNull ClearCredentialStateRequest request) {
275         if (mCallback == null) {
276             Slog.w(TAG, "Callback is not set");
277             return;
278         }
279         mOngoingRequest.set(true);
280 
281         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
282         AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
283 
284         CompletableFuture<Void> connectThenExecute =
285                 postAsync(service -> {
286                     CompletableFuture<Void> clearCredentialFuture =
287                             new CompletableFuture<>();
288                     final long originalCallingUidToken = Binder.clearCallingIdentity();
289                     try {
290                         service.onClearCredentialState(
291                                 request, new IClearCredentialStateCallback.Stub() {
292                                     @Override
293                                     public void onSuccess() {
294                                         clearCredentialFuture.complete(null);
295                                     }
296 
297                                     @Override
298                                     public void onFailure(String errorType, CharSequence message) {
299                                         String errorMsg = message == null ? "" :
300                                                 String.valueOf(message);
301                                         clearCredentialFuture.completeExceptionally(
302                                                 new ClearCredentialStateException(errorType,
303                                                         errorMsg));
304                                     }
305 
306                                     @Override
307                                     public void onCancellable(ICancellationSignal cancellation) {
308                                         CompletableFuture<Void> future = futureRef.get();
309                                         if (future != null && future.isCancelled()) {
310                                             dispatchCancellationSignal(cancellation);
311                                         } else {
312                                             cancellationSink.set(cancellation);
313                                             if (mCallback != null) {
314                                                 mCallback.onProviderCancellable(cancellation);
315                                             }
316                                         }
317                                     }
318                                 });
319                         return clearCredentialFuture;
320                     } finally {
321                         Binder.restoreCallingIdentity(originalCallingUidToken);
322                     }
323                 }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
324         futureRef.set(connectThenExecute);
325 
326         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
327                 handleExecutionResponse(result, error, cancellationSink)));
328     }
329 
handleExecutionResponse(T result, Throwable error, AtomicReference<ICancellationSignal> cancellationSink)330     private <T> void handleExecutionResponse(T result,
331             Throwable error,
332             AtomicReference<ICancellationSignal> cancellationSink) {
333         if (error == null) {
334             if (mCallback != null) {
335                 mCallback.onProviderResponseSuccess(result);
336             }
337         } else {
338             if (error instanceof TimeoutException) {
339                 Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName);
340                 if (!mOngoingRequest.get()) {
341                     return;
342                 }
343                 dispatchCancellationSignal(cancellationSink.get());
344                 if (mCallback != null) {
345                     mOngoingRequest.set(false);
346                     mCallback.onProviderResponseFailure(
347                             CredentialProviderErrors.ERROR_TIMEOUT, null);
348                 }
349             } else if (error instanceof CancellationException) {
350                 Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName);
351                 if (!mOngoingRequest.get()) {
352                     return;
353                 }
354                 dispatchCancellationSignal(cancellationSink.get());
355                 if (mCallback != null) {
356                     mOngoingRequest.set(false);
357                     mCallback.onProviderResponseFailure(
358                             CredentialProviderErrors.ERROR_TASK_CANCELED,
359                             null);
360                 }
361             } else if (error instanceof GetCredentialException) {
362                 if (mCallback != null) {
363                     mCallback.onProviderResponseFailure(
364                             CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
365                             (GetCredentialException) error);
366                 }
367             } else if (error instanceof CreateCredentialException) {
368                 if (mCallback != null) {
369                     mCallback.onProviderResponseFailure(
370                             CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
371                             (CreateCredentialException) error);
372                 }
373             } else {
374                 if (mCallback != null) {
375                     mCallback.onProviderResponseFailure(
376                             CredentialProviderErrors.ERROR_UNKNOWN,
377                             (Exception) error);
378                 }
379             }
380         }
381     }
382 
dispatchCancellationSignal(@ullable ICancellationSignal signal)383     private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
384         if (signal == null) {
385             Slog.e(TAG, "Error dispatching a cancellation - Signal is null");
386             return;
387         }
388         try {
389             signal.cancel();
390         } catch (RemoteException e) {
391             Slog.e(TAG, "Error dispatching a cancellation", e);
392         }
393     }
394 }
395