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 android.service.credentials;
18 
19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20 
21 import android.annotation.CallSuper;
22 import android.annotation.NonNull;
23 import android.annotation.SdkConstant;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.Intent;
27 import android.credentials.ClearCredentialStateException;
28 import android.credentials.CreateCredentialException;
29 import android.credentials.GetCredentialException;
30 import android.os.CancellationSignal;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.ICancellationSignal;
34 import android.os.Looper;
35 import android.os.OutcomeReceiver;
36 import android.os.RemoteException;
37 import android.util.Slog;
38 
39 import java.util.Objects;
40 
41 /**
42  * Service to be extended by credential providers, in order to return user credentials
43  * to the framework.
44  */
45 public abstract class CredentialProviderService extends Service {
46     /**
47      * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
48      * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
49      * returned as part of the {@link BeginCreateCredentialResponse}
50      *
51      * <p>
52      * Type: {@link android.service.credentials.CreateCredentialRequest}
53      */
54     public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
55             "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
56 
57     /**
58      * Intent extra: The {@link GetCredentialRequest} attached with
59      * the {@code pendingIntent} that is invoked when the user selects a {@link CredentialEntry}
60      * returned as part of the {@link BeginGetCredentialResponse}
61      *
62      * <p>
63      * Type: {@link GetCredentialRequest}
64      */
65     public static final String EXTRA_GET_CREDENTIAL_REQUEST =
66             "android.service.credentials.extra.GET_CREDENTIAL_REQUEST";
67 
68     /**
69      * Intent extra: The result of a create flow operation, to be set on finish of the
70      * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
71      * a {@link CreateEntry}.
72      *
73      * <p>
74      * Type: {@link android.credentials.CreateCredentialResponse}
75      */
76     public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
77             "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
78 
79     /**
80      * Intent extra: The result of a get credential flow operation, to be set on finish of the
81      * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
82      * a {@link CredentialEntry}.
83      *
84      * <p>
85      * Type: {@link android.credentials.GetCredentialResponse}
86      */
87     public static final String EXTRA_GET_CREDENTIAL_RESPONSE =
88             "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE";
89 
90     /**
91      * Intent extra: The result of an authentication flow, to be set on finish of the
92      * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
93      * an authentication {@link Action}, as part of the original
94      * {@link BeginGetCredentialResponse}. This result should contain the actual content,
95      * including credential entries and action entries, to be shown on the selector.
96      *
97      * <p>
98      * Type: {@link BeginGetCredentialResponse}
99      */
100     public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE =
101             "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
102 
103     /**
104      * Intent extra: The failure exception set at the final stage of a get flow.
105      * This exception is set at the finishing result of the {@link android.app.Activity}
106      * invoked by the {@link PendingIntent} , when a user selects the {@link CredentialEntry}
107      * that contained the {@link PendingIntent} in question.
108      *
109      * <p>The result must be set through {@link android.app.Activity#setResult} as an intent extra
110      *
111      * <p>
112      * Type: {@link android.credentials.GetCredentialException}
113      */
114     public static final String EXTRA_GET_CREDENTIAL_EXCEPTION =
115             "android.service.credentials.extra.GET_CREDENTIAL_EXCEPTION";
116 
117     /**
118      * Intent extra: The failure exception set at the final stage of a create flow.
119      * This exception is set at the finishing result of the {@link android.app.Activity}
120      * invoked by the {@link PendingIntent} , when a user selects the {@link CreateEntry}
121      * that contained the {@link PendingIntent} in question.
122      *
123      * <p>
124      * Type: {@link android.credentials.CreateCredentialException}
125      */
126     public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION =
127             "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
128 
129     /**
130      * Intent extra: The {@link BeginGetCredentialRequest} attached with
131      * the {@code pendingIntent} that is invoked when the user selects an
132      * authentication entry (intending to unlock the provider app) on the UI.
133      *
134      * <p>When a provider app receives a {@link BeginGetCredentialRequest} through the
135      * {@link CredentialProviderService#onBeginGetCredential} call, it can construct the
136      * {@link BeginGetCredentialResponse} with either an authentication {@link Action} (if the app
137      * is locked), or a {@link BeginGetCredentialResponse} (if the app is unlocked). In the former
138      * case, i.e. the app is locked, user will be shown the authentication action. When selected,
139      * the underlying {@link PendingIntent} will be invoked which will lead the user to provider's
140      * unlock activity. This pending intent will also contain the original
141      * {@link BeginGetCredentialRequest} to be retrieved and processed after the unlock
142      * flow is complete.
143      *
144      * <p>After the app is unlocked, the {@link BeginGetCredentialResponse} must be constructed
145      * using a {@link BeginGetCredentialResponse}, which must be set on an {@link Intent} as an
146      * intent extra against CredentialProviderService#EXTRA_CREDENTIALS_RESPONSE_CONTENT}.
147      * This intent should then be set as a result through {@link android.app.Activity#setResult}
148      * before finishing the activity.
149      *
150      * <p>
151      * Type: {@link BeginGetCredentialRequest}
152      */
153     public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST =
154             "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
155 
156     private static final String TAG = "CredProviderService";
157 
158      /**
159       * Name under which a Credential Provider service component publishes information
160       * about itself.  This meta-data must reference an XML resource containing
161       * an
162       * <code>&lt;{@link android.R.styleable#CredentialProvider credential-provider}&gt;</code>
163       * tag.
164       *
165       * For example (AndroidManifest.xml):
166       * <code>
167       * <meta-data
168       *         android:name="android.credentials.provider"
169       *          android:resource="@xml/provider"/>
170       * </code>
171       *
172       * For example (xml/provider.xml):
173       * <code>
174       * <credential-provider xmlns:android="http://schemas.android.com/apk/res/android"
175       *       android:settingsSubtitle="@string/providerSubtitle">
176       *      <capabilities>
177       *          <capability>@string/passwords</capability>
178       *          <capability>@string/passkeys</capability>
179       *      </capabilities>
180       *      <string name="passwords">android.credentials.TYPE_PASSWORD_CREDENTIAL</string>
181       *      <string name="passkeys">android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL</string>
182       *  </credential-provider>
183       * </code>
184       */
185     public static final String SERVICE_META_DATA = "android.credentials.provider";
186 
187     /** @hide */
188     public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY =
189             "android.credentials.testsystemprovider";
190 
191     private Handler mHandler;
192 
193     /**
194      * The {@link Intent} that must be declared as handled by the service. The service must also
195      * require the {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission
196      * so that only the system can bind to it.
197      */
198     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
199     public static final String SERVICE_INTERFACE =
200             "android.service.credentials.CredentialProviderService";
201 
202     /**
203      * The {@link Intent} that must be declared as handled by a system credential provider
204      * service.
205      *
206      * <p>The service must also require the
207      * {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission
208      * so that only the system can bind to it.
209      *
210      * @hide
211      */
212     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
213     public static final String SYSTEM_SERVICE_INTERFACE =
214             "android.service.credentials.system.CredentialProviderService";
215 
216     @CallSuper
217     @Override
onCreate()218     public void onCreate() {
219         super.onCreate();
220         mHandler = new Handler(Looper.getMainLooper(), null, true);
221     }
222 
223     @Override
onBind(@onNull Intent intent)224     @NonNull public final IBinder onBind(@NonNull Intent intent) {
225         if (SERVICE_INTERFACE.equals(intent.getAction())) {
226             return mInterface.asBinder();
227         }
228         Slog.w(TAG, "Failed to bind with intent: " + intent);
229         return null;
230     }
231 
232     private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
233         @Override
234         public void onBeginGetCredential(BeginGetCredentialRequest request,
235                 IBeginGetCredentialCallback callback) {
236             Objects.requireNonNull(request);
237             Objects.requireNonNull(callback);
238 
239             ICancellationSignal transport = CancellationSignal.createTransport();
240             try {
241                 callback.onCancellable(transport);
242             } catch (RemoteException e) {
243                 e.rethrowFromSystemServer();
244             }
245 
246             mHandler.sendMessage(obtainMessage(
247                     CredentialProviderService::onBeginGetCredential,
248                     CredentialProviderService.this, request,
249                     CancellationSignal.fromTransport(transport),
250                     new OutcomeReceiver<BeginGetCredentialResponse,
251                             GetCredentialException>() {
252                         @Override
253                         public void onResult(BeginGetCredentialResponse result) {
254                             try {
255                                 callback.onSuccess(result);
256                             } catch (RemoteException e) {
257                                 e.rethrowFromSystemServer();
258                             }
259                         }
260                         @Override
261                         public void onError(GetCredentialException e) {
262                             try {
263                                 callback.onFailure(e.getType(), e.getMessage());
264                             } catch (RemoteException ex) {
265                                 ex.rethrowFromSystemServer();
266                             }
267                         }
268                     }
269             ));
270         }
271 
272         @Override
273         public void onBeginCreateCredential(BeginCreateCredentialRequest request,
274                 IBeginCreateCredentialCallback callback) {
275             Objects.requireNonNull(request);
276             Objects.requireNonNull(callback);
277 
278             ICancellationSignal transport = CancellationSignal.createTransport();
279             try {
280                 callback.onCancellable(transport);
281             } catch (RemoteException e) {
282                 e.rethrowFromSystemServer();
283             }
284 
285             mHandler.sendMessage(obtainMessage(
286                     CredentialProviderService::onBeginCreateCredential,
287                     CredentialProviderService.this, request,
288                     CancellationSignal.fromTransport(transport),
289                     new OutcomeReceiver<
290                             BeginCreateCredentialResponse, CreateCredentialException>() {
291                         @Override
292                         public void onResult(BeginCreateCredentialResponse result) {
293                             try {
294                                 callback.onSuccess(result);
295                             } catch (RemoteException e) {
296                                 e.rethrowFromSystemServer();
297                             }
298                         }
299                         @Override
300                         public void onError(CreateCredentialException e) {
301                             try {
302                                 callback.onFailure(e.getType(), e.getMessage());
303                             } catch (RemoteException ex) {
304                                 ex.rethrowFromSystemServer();
305                             }
306                         }
307                     }
308             ));
309         }
310 
311         @Override
312         public void onClearCredentialState(ClearCredentialStateRequest request,
313                 IClearCredentialStateCallback callback) {
314             Objects.requireNonNull(request);
315             Objects.requireNonNull(callback);
316 
317             ICancellationSignal transport = CancellationSignal.createTransport();
318             try {
319                 callback.onCancellable(transport);
320             } catch (RemoteException e) {
321                 e.rethrowFromSystemServer();
322             }
323 
324             mHandler.sendMessage(obtainMessage(
325                     CredentialProviderService::onClearCredentialState,
326                     CredentialProviderService.this, request,
327                     CancellationSignal.fromTransport(transport),
328                     new OutcomeReceiver<Void, ClearCredentialStateException>() {
329                         @Override
330                         public void onResult(Void result) {
331                             try {
332                                 callback.onSuccess();
333                             } catch (RemoteException e) {
334                                 e.rethrowFromSystemServer();
335                             }
336                         }
337                         @Override
338                         public void onError(ClearCredentialStateException e) {
339                             try {
340                                 callback.onFailure(e.getType(), e.getMessage());
341                             } catch (RemoteException ex) {
342                                 ex.rethrowFromSystemServer();
343                             }
344                         }
345                     }
346             ));
347         }
348     };
349 
350     /**
351      * Called by the android system to retrieve user credentials from the connected provider
352      * service.
353      *
354      * <p>This API denotes a query stage request for getting user's credentials from a given
355      * credential provider. The request contains a list of
356      * {@link BeginGetCredentialOption} that have parameters to be used for
357      * populating candidate credentials, as a list of {@link CredentialEntry} to be set
358      * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a
359      * selector.
360      *
361      * <p>If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that
362      * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual
363      * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired
364      * through the {@link PendingIntent}.
365      * @param request the request for the provider to handle
366      * @param cancellationSignal signal for providers to listen to any cancellation requests from
367      *                           the android system
368      * @param callback object used to relay the response of the credentials request
369      */
onBeginGetCredential(@onNull BeginGetCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver< BeginGetCredentialResponse, GetCredentialException> callback)370     public abstract void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
371             @NonNull CancellationSignal cancellationSignal,
372             @NonNull OutcomeReceiver<
373                     BeginGetCredentialResponse, GetCredentialException> callback);
374 
375     /**
376      * Called by the android system to create a credential.
377      * @param request The credential creation request for the provider to handle.
378      * @param cancellationSignal Signal for providers to listen to any cancellation requests from
379      *                           the android system.
380      * @param callback Object used to relay the response of the credential creation request.
381      */
onBeginCreateCredential(@onNull BeginCreateCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> callback)382     public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
383             @NonNull CancellationSignal cancellationSignal,
384             @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
385                     CreateCredentialException> callback);
386 
387     /**
388      * Called by the android system to clear the credential state.
389      *
390      * This api isinvoked by developers after users sign out of an app, with an intention to
391      * clear any stored credential session that providers have retained.
392      *
393      * As a provider, you must clear any credential state, if maintained. For e.g. a provider may
394      * have stored an active credential session that is used to limit or rank sign-in options for
395      * future credential retrieval flows. When a user signs out of the app, such state should be
396      * cleared and an exhaustive list of credentials must be presented to the user on subsequent
397      * credential retrieval flows.
398      *
399      * @param request The clear credential request for the provider to handle.
400      * @param cancellationSignal Signal for providers to listen to any cancellation requests from
401      *                           the android system.
402      * @param callback Object used to relay the result of the request.
403      */
onClearCredentialState(@onNull ClearCredentialStateRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback)404     public abstract void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
405             @NonNull CancellationSignal cancellationSignal,
406             @NonNull OutcomeReceiver<Void,
407                     ClearCredentialStateException> callback);
408 }
409