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