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 /** 157 * The key to autofillId associated with the requested credential option and the corresponding 158 * credential entry. The associated autofillId will be contained inside the candidate query 159 * bundle of {@link android.credentials.CredentialOption} if requested through the 160 * {@link com.android.credentialmanager.autofill.CredentialAutofillService}. The resulting 161 * credential entry will contain the autofillId inside its framework extras intent. 162 * 163 * @hide 164 */ 165 public static final String EXTRA_AUTOFILL_ID = 166 "android.service.credentials.extra.AUTOFILL_ID"; 167 168 private static final String TAG = "CredProviderService"; 169 170 /** 171 * Name under which a Credential Provider service component publishes information 172 * about itself. This meta-data must reference an XML resource containing 173 * an 174 * <code><{@link android.R.styleable#CredentialProvider credential-provider}></code> 175 * tag. 176 * 177 * For example (AndroidManifest.xml): 178 * <code> 179 * <meta-data 180 * android:name="android.credentials.provider" 181 * android:resource="@xml/provider"/> 182 * </code> 183 * 184 * For example (xml/provider.xml): 185 * <code> 186 * <credential-provider xmlns:android="http://schemas.android.com/apk/res/android" 187 * android:settingsSubtitle="@string/providerSubtitle"> 188 * <capabilities> 189 * <capability>@string/passwords</capability> 190 * <capability>@string/passkeys</capability> 191 * </capabilities> 192 * <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" /> 193 * <capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" /> 194 * </credential-provider> 195 * </code> 196 */ 197 public static final String SERVICE_META_DATA = "android.credentials.provider"; 198 199 /** @hide */ 200 public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY = 201 "android.credentials.testsystemprovider"; 202 203 private Handler mHandler; 204 205 /** 206 * The {@link Intent} that must be declared as handled by the service. The service must also 207 * require the {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission 208 * so that only the system can bind to it. 209 */ 210 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 211 public static final String SERVICE_INTERFACE = 212 "android.service.credentials.CredentialProviderService"; 213 214 /** 215 * The {@link Intent} that must be declared as handled by a system credential provider 216 * service. 217 * 218 * <p>The service must also require the 219 * {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission 220 * so that only the system can bind to it. 221 * 222 * @hide 223 */ 224 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 225 public static final String SYSTEM_SERVICE_INTERFACE = 226 "android.service.credentials.system.CredentialProviderService"; 227 228 @CallSuper 229 @Override onCreate()230 public void onCreate() { 231 super.onCreate(); 232 mHandler = new Handler(Looper.getMainLooper(), null, true); 233 } 234 235 @Override onBind(@onNull Intent intent)236 @NonNull public final IBinder onBind(@NonNull Intent intent) { 237 if (SERVICE_INTERFACE.equals(intent.getAction())) { 238 return mInterface.asBinder(); 239 } 240 Slog.w(TAG, "Failed to bind with intent: " + intent); 241 return null; 242 } 243 244 private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() { 245 @Override 246 public void onBeginGetCredential(BeginGetCredentialRequest request, 247 IBeginGetCredentialCallback callback) { 248 Objects.requireNonNull(request); 249 Objects.requireNonNull(callback); 250 251 ICancellationSignal transport = CancellationSignal.createTransport(); 252 try { 253 callback.onCancellable(transport); 254 } catch (RemoteException e) { 255 e.rethrowFromSystemServer(); 256 } 257 258 mHandler.sendMessage(obtainMessage( 259 CredentialProviderService::onBeginGetCredential, 260 CredentialProviderService.this, request, 261 CancellationSignal.fromTransport(transport), 262 new OutcomeReceiver<BeginGetCredentialResponse, 263 GetCredentialException>() { 264 @Override 265 public void onResult(BeginGetCredentialResponse result) { 266 try { 267 callback.onSuccess(result); 268 } catch (RemoteException e) { 269 e.rethrowFromSystemServer(); 270 } 271 } 272 @Override 273 public void onError(GetCredentialException e) { 274 try { 275 callback.onFailure(e.getType(), e.getMessage()); 276 } catch (RemoteException ex) { 277 ex.rethrowFromSystemServer(); 278 } 279 } 280 } 281 )); 282 } 283 284 @Override 285 public void onBeginCreateCredential(BeginCreateCredentialRequest request, 286 IBeginCreateCredentialCallback callback) { 287 Objects.requireNonNull(request); 288 Objects.requireNonNull(callback); 289 290 ICancellationSignal transport = CancellationSignal.createTransport(); 291 try { 292 callback.onCancellable(transport); 293 } catch (RemoteException e) { 294 e.rethrowFromSystemServer(); 295 } 296 297 mHandler.sendMessage(obtainMessage( 298 CredentialProviderService::onBeginCreateCredential, 299 CredentialProviderService.this, request, 300 CancellationSignal.fromTransport(transport), 301 new OutcomeReceiver< 302 BeginCreateCredentialResponse, CreateCredentialException>() { 303 @Override 304 public void onResult(BeginCreateCredentialResponse result) { 305 try { 306 callback.onSuccess(result); 307 } catch (RemoteException e) { 308 e.rethrowFromSystemServer(); 309 } 310 } 311 @Override 312 public void onError(CreateCredentialException e) { 313 try { 314 callback.onFailure(e.getType(), e.getMessage()); 315 } catch (RemoteException ex) { 316 ex.rethrowFromSystemServer(); 317 } 318 } 319 } 320 )); 321 } 322 323 @Override 324 public void onClearCredentialState(ClearCredentialStateRequest request, 325 IClearCredentialStateCallback callback) { 326 Objects.requireNonNull(request); 327 Objects.requireNonNull(callback); 328 329 ICancellationSignal transport = CancellationSignal.createTransport(); 330 try { 331 callback.onCancellable(transport); 332 } catch (RemoteException e) { 333 e.rethrowFromSystemServer(); 334 } 335 336 mHandler.sendMessage(obtainMessage( 337 CredentialProviderService::onClearCredentialState, 338 CredentialProviderService.this, request, 339 CancellationSignal.fromTransport(transport), 340 new OutcomeReceiver<Void, ClearCredentialStateException>() { 341 @Override 342 public void onResult(Void result) { 343 try { 344 callback.onSuccess(); 345 } catch (RemoteException e) { 346 e.rethrowFromSystemServer(); 347 } 348 } 349 @Override 350 public void onError(ClearCredentialStateException e) { 351 try { 352 callback.onFailure(e.getType(), e.getMessage()); 353 } catch (RemoteException ex) { 354 ex.rethrowFromSystemServer(); 355 } 356 } 357 } 358 )); 359 } 360 }; 361 362 /** 363 * Called by the android system to retrieve user credentials from the connected provider 364 * service. 365 * 366 * <p>This API denotes a query stage request for getting user's credentials from a given 367 * credential provider. The request contains a list of 368 * {@link BeginGetCredentialOption} that have parameters to be used for 369 * populating candidate credentials, as a list of {@link CredentialEntry} to be set 370 * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a 371 * selector. 372 * 373 * <p>If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that 374 * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual 375 * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired 376 * through the {@link PendingIntent}. 377 * @param request the 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 credentials request 381 */ onBeginGetCredential(@onNull BeginGetCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver< BeginGetCredentialResponse, GetCredentialException> callback)382 public abstract void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, 383 @NonNull CancellationSignal cancellationSignal, 384 @NonNull OutcomeReceiver< 385 BeginGetCredentialResponse, GetCredentialException> callback); 386 387 /** 388 * Called by the android system to create a credential. 389 * @param request The credential creation request for the provider to handle. 390 * @param cancellationSignal Signal for providers to listen to any cancellation requests from 391 * the android system. 392 * @param callback Object used to relay the response of the credential creation request. 393 */ onBeginCreateCredential(@onNull BeginCreateCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> callback)394 public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, 395 @NonNull CancellationSignal cancellationSignal, 396 @NonNull OutcomeReceiver<BeginCreateCredentialResponse, 397 CreateCredentialException> callback); 398 399 /** 400 * Called by the android system to clear the credential state. 401 * 402 * This api isinvoked by developers after users sign out of an app, with an intention to 403 * clear any stored credential session that providers have retained. 404 * 405 * As a provider, you must clear any credential state, if maintained. For e.g. a provider may 406 * have stored an active credential session that is used to limit or rank sign-in options for 407 * future credential retrieval flows. When a user signs out of the app, such state should be 408 * cleared and an exhaustive list of credentials must be presented to the user on subsequent 409 * credential retrieval flows. 410 * 411 * @param request The clear credential request for the provider to handle. 412 * @param cancellationSignal Signal for providers to listen to any cancellation requests from 413 * the android system. 414 * @param callback Object used to relay the result of the request. 415 */ onClearCredentialState(@onNull ClearCredentialStateRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback)416 public abstract void onClearCredentialState(@NonNull ClearCredentialStateRequest request, 417 @NonNull CancellationSignal cancellationSignal, 418 @NonNull OutcomeReceiver<Void, 419 ClearCredentialStateException> callback); 420 } 421