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><{@link android.R.styleable#CredentialProvider credential-provider}></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