diff --git a/identity/credentialmanager/build.gradle.kts b/identity/credentialmanager/build.gradle.kts index fd4d43e1..5ed3c0c5 100644 --- a/identity/credentialmanager/build.gradle.kts +++ b/identity/credentialmanager/build.gradle.kts @@ -48,6 +48,11 @@ dependencies { implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) + + // [START android_identity_credman_dependency] + implementation(libs.androidx.credentials) + // [END android_identity_credman_dependency] + // [START android_identity_gradle_dependencies] implementation(libs.androidx.credentials) @@ -62,4 +67,4 @@ dependencies { // [END android_identity_siwg_gradle_dependencies] debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.test.manifest) -} \ No newline at end of file +} diff --git a/identity/credentialmanager/src/main/AndroidManifest.xml b/identity/credentialmanager/src/main/AndroidManifest.xml index 70391952..a2ec1cef 100644 --- a/identity/credentialmanager/src/main/AndroidManifest.xml +++ b/identity/credentialmanager/src/main/AndroidManifest.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + + + + + + + + + + + - \ No newline at end of file + diff --git a/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt new file mode 100644 index 00000000..04e4fb91 --- /dev/null +++ b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/CredentialProviderDummyActivity.kt @@ -0,0 +1,491 @@ +package com.example.identity.credentialmanager + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.os.Build.VERSION_CODES +import android.os.Bundle +import android.os.PersistableBundle +import androidx.annotation.RequiresApi +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.biometric.BiometricPrompt.AuthenticationCallback +import androidx.biometric.BiometricPrompt.AuthenticationResult +import androidx.credentials.CreatePasswordRequest +import androidx.credentials.CreatePasswordResponse +import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.CreatePublicKeyCredentialResponse +import androidx.credentials.GetCredentialResponse +import androidx.credentials.GetPasswordOption +import androidx.credentials.GetPublicKeyCredentialOption +import androidx.credentials.PasswordCredential +import androidx.credentials.PublicKeyCredential +import androidx.credentials.provider.BeginCreateCredentialRequest +import androidx.credentials.provider.BeginCreateCredentialResponse +import androidx.credentials.provider.BeginCreatePasswordCredentialRequest +import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest +import androidx.credentials.provider.CallingAppInfo +import androidx.credentials.provider.CreateEntry +import androidx.credentials.provider.PendingIntentHandler +import androidx.credentials.webauthn.AuthenticatorAssertionResponse +import androidx.credentials.webauthn.AuthenticatorAttestationResponse +import androidx.credentials.webauthn.FidoPublicKeyCredential +import androidx.credentials.webauthn.PublicKeyCredentialCreationOptions +import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions +import androidx.fragment.app.FragmentActivity +import java.math.BigInteger +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.MessageDigest +import java.security.SecureRandom +import java.security.Signature +import java.security.interfaces.ECPrivateKey +import java.security.spec.ECGenParameterSpec +import java.security.spec.ECParameterSpec +import java.security.spec.ECPoint +import java.security.spec.EllipticCurve + +class CredentialProviderDummyActivity: FragmentActivity() { + + private val PERSONAL_ACCOUNT_ID: String = "" + private val FAMILY_ACCOUNT_ID: String = "" + private val CREATE_PASSWORD_INTENT: String = "" + + @RequiresApi(VERSION_CODES.M) + // [START android_identity_credential_provider_handle_passkey] + override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + super.onCreate(savedInstanceState, persistentState) + // ... + + val request = + PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent) + + val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID) + if (request != null && request.callingRequest is CreatePublicKeyCredentialRequest) { + val publicKeyRequest: CreatePublicKeyCredentialRequest = + request.callingRequest as CreatePublicKeyCredentialRequest + createPasskey( + publicKeyRequest.requestJson, + request.callingAppInfo, + publicKeyRequest.clientDataHash, + accountId + ) + } + } + + @SuppressLint("RestrictedApi") + fun createPasskey( + requestJson: String, + callingAppInfo: CallingAppInfo?, + clientDataHash: ByteArray?, + accountId: String? + ) { + val request = PublicKeyCredentialCreationOptions(requestJson) + + val biometricPrompt = BiometricPrompt( + this, + { }, // Pass in your own executor + object : AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + finish() + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + finish() + } + + @RequiresApi(VERSION_CODES.P) + override fun onAuthenticationSucceeded( + result: AuthenticationResult + ) { + super.onAuthenticationSucceeded(result) + + // Generate a credentialId + val credentialId = ByteArray(32) + SecureRandom().nextBytes(credentialId) + + // Generate a credential key pair + val spec = ECGenParameterSpec("secp256r1") + val keyPairGen = KeyPairGenerator.getInstance("EC"); + keyPairGen.initialize(spec) + val keyPair = keyPairGen.genKeyPair() + + // Save passkey in your database as per your own implementation + + // Create AuthenticatorAttestationResponse object to pass to + // FidoPublicKeyCredential + + val response = AuthenticatorAttestationResponse( + requestOptions = request, + credentialId = credentialId, + credentialPublicKey = getPublicKeyFromKeyPair(keyPair), + origin = appInfoToOrigin(callingAppInfo!!), + up = true, + uv = true, + be = true, + bs = true, + packageName = callingAppInfo.packageName + ) + + val credential = FidoPublicKeyCredential( + rawId = credentialId, + response = response, + authenticatorAttachment = "", // Add your authenticator attachment + ) + val result = Intent() + + val createPublicKeyCredResponse = + CreatePublicKeyCredentialResponse(credential.json()) + + // Set the CreateCredentialResponse as the result of the Activity + PendingIntentHandler.setCreateCredentialResponse( + result, + createPublicKeyCredResponse + ) + setResult(RESULT_OK, result) + finish() + } + } + ) + + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle("Use your screen lock") + .setSubtitle("Create passkey for ${request.rp.name}") + .setAllowedAuthenticators( + BiometricManager.Authenticators.BIOMETRIC_STRONG + /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */ + ) + .build() + biometricPrompt.authenticate(promptInfo) + } + + @RequiresApi(VERSION_CODES.P) + fun appInfoToOrigin(info: CallingAppInfo): String { + val cert = info.signingInfo.apkContentsSigners[0].toByteArray() + val md = MessageDigest.getInstance("SHA-256"); + val certHash = md.digest(cert) + // This is the format for origin + return "android:apk-key-hash:${b64Encode(certHash)}" + } + // [END android_identity_credential_provider_handle_passkey] + + @RequiresApi(VERSION_CODES.M) + // [START android_identity_credential_provider_password_creation] + fun processCreateCredentialRequest( + request: BeginCreateCredentialRequest + ): BeginCreateCredentialResponse? { + when (request) { + is BeginCreatePublicKeyCredentialRequest -> { + // Request is passkey type + return handleCreatePasskeyQuery(request) + } + + is BeginCreatePasswordCredentialRequest -> { + // Request is password type + return handleCreatePasswordQuery(request) + } + } + return null + } + + @RequiresApi(VERSION_CODES.M) + private fun handleCreatePasswordQuery( + request: BeginCreatePasswordCredentialRequest + ): BeginCreateCredentialResponse { + val createEntries: MutableList = mutableListOf() + + // Adding two create entries - one for storing credentials to the 'Personal' + // account, and one for storing them to the 'Family' account. These + // accounts are local to this sample app only. + createEntries.add( + CreateEntry( + PERSONAL_ACCOUNT_ID, + createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSWORD_INTENT) + ) + ) + createEntries.add( + CreateEntry( + FAMILY_ACCOUNT_ID, + createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSWORD_INTENT) + ) + ) + + return BeginCreateCredentialResponse(createEntries) + } + // [END android_identity_credential_provider_password_creation] + + @RequiresApi(VERSION_CODES.M) + fun handleEntrySelectionForPasswordCreation( + mDatabase: MyDatabase + ) { + // [START android_identity_credential_provider_entry_selection_password_creation] + val createRequest = PendingIntentHandler.retrieveProviderCreateCredentialRequest(intent) + val accountId = intent.getStringExtra(CredentialsRepo.EXTRA_KEY_ACCOUNT_ID) + + if (createRequest == null) { + return + } + + val request: CreatePasswordRequest = createRequest.callingRequest as CreatePasswordRequest + + // Fetch the ID and password from the request and save it in your database + mDatabase.addNewPassword( + PasswordInfo( + request.id, + request.password, + createRequest.callingAppInfo.packageName + ) + ) + + //Set the final response back + val result = Intent() + val response = CreatePasswordResponse() + PendingIntentHandler.setCreateCredentialResponse(result, response) + setResult(Activity.RESULT_OK, result) + finish() + // [END android_identity_credential_provider_entry_selection_password_creation] + } + + @RequiresApi(VERSION_CODES.P) + private fun handleUserSelectionForPasskeys( + mDatabase: MyDatabase + ) { + // [START android_identity_credential_provider_user_pk_selection] + val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) + val publicKeyRequest = getRequest?.credentialOptions?.first() as GetPublicKeyCredentialOption + + val requestInfo = intent.getBundleExtra("CREDENTIAL_DATA") + val credIdEnc = requestInfo?.getString("credId").orEmpty() + + // Get the saved passkey from your database based on the credential ID from the PublicKeyRequest + val passkey = mDatabase.getPasskey(credIdEnc) + + // Decode the credential ID, private key and user ID + val credId = b64Decode(credIdEnc) + val privateKey = b64Decode(passkey.credPrivateKey) + val uid = b64Decode(passkey.uid) + + val origin = appInfoToOrigin(getRequest.callingAppInfo) + val packageName = getRequest.callingAppInfo.packageName + + validatePasskey( + publicKeyRequest.requestJson, + origin, + packageName, + uid, + passkey.username, + credId, + privateKey + ) + // [END android_identity_credential_provider_user_pk_selection] + } + + @SuppressLint("RestrictedApi") + @RequiresApi(VERSION_CODES.M) + private fun validatePasskey( + requestJson: String, + origin: String, + packageName: String, + uid: ByteArray, + username: String, + credId: ByteArray, + privateKeyBytes: ByteArray, + ) { + // [START android_identity_credential_provider_user_validation_biometric] + val request = PublicKeyCredentialRequestOptions(requestJson) + val privateKey: ECPrivateKey = convertPrivateKey(privateKeyBytes) + + val biometricPrompt = BiometricPrompt( + this, + { }, // Pass in your own executor + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError( + errorCode: Int, errString: CharSequence + ) { + super.onAuthenticationError(errorCode, errString) + finish() + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + finish() + } + + override fun onAuthenticationSucceeded( + result: BiometricPrompt.AuthenticationResult + ) { + super.onAuthenticationSucceeded(result) + val response = AuthenticatorAssertionResponse( + requestOptions = request, + credentialId = credId, + origin = origin, + up = true, + uv = true, + be = true, + bs = true, + userHandle = uid, + packageName = packageName + ) + + val sig = Signature.getInstance("SHA256withECDSA"); + sig.initSign(privateKey) + sig.update(response.dataToSign()) + response.signature = sig.sign() + + val credential = FidoPublicKeyCredential( + rawId = credId, + response = response, + authenticatorAttachment = "", // Add your authenticator attachment + ) + val result = Intent() + val passkeyCredential = PublicKeyCredential(credential.json()) + PendingIntentHandler.setGetCredentialResponse( + result, GetCredentialResponse(passkeyCredential) + ) + setResult(RESULT_OK, result) + finish() + } + } + ) + + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle("Use your screen lock") + .setSubtitle("Use passkey for ${request.rpId}") + .setAllowedAuthenticators( + BiometricManager.Authenticators.BIOMETRIC_STRONG + /* or BiometricManager.Authenticators.DEVICE_CREDENTIAL */ + ) + .build() + biometricPrompt.authenticate(promptInfo) + // [END android_identity_credential_provider_user_validation_biometric] + } + + @RequiresApi(VERSION_CODES.M) + private fun handleUserSelectionForPasswordAuthentication( + mDatabase: MyDatabase, + callingAppInfo: CallingAppInfo, + ) { + // [START android_identity_credential_provider_user_selection_password] + val getRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent) + + val passwordOption = getRequest?.credentialOptions?.first() as GetPasswordOption + + val username = passwordOption.allowedUserIds.first() + // Fetch the credentials for the calling app package name + val creds = mDatabase.getCredentials(callingAppInfo.packageName) + val passwords = creds.passwords + val it = passwords.iterator() + var password = "" + while (it.hasNext()) { + val passwordItemCurrent = it.next() + if (passwordItemCurrent.username == username) { + password = passwordItemCurrent.password + break + } + } + // [END android_identity_credential_provider_user_selection_password] + + // [START android_identity_credential_provider_set_response] + // Set the response back + val result = Intent() + val passwordCredential = PasswordCredential(username, password) + PendingIntentHandler.setGetCredentialResponse( + result, GetCredentialResponse(passwordCredential) + ) + setResult(Activity.RESULT_OK, result) + finish() + // [END android_identity_credential_provider_set_response] + } + + // [START android_identity_credential_pending_intent] + fun createSettingsPendingIntent(): PendingIntent + // [END android_identity_credential_pending_intent] + { + return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) + } + + private fun getPublicKeyFromKeyPair(keyPair: KeyPair): ByteArray { + return byteArrayOf() + } + + private fun b64Encode(certHash: ByteArray) {} + + private fun handleCreatePasskeyQuery( + request: BeginCreatePublicKeyCredentialRequest + ): BeginCreateCredentialResponse { + return BeginCreateCredentialResponse() + } + + private fun createNewPendingIntent( + accountId: String, + intent: String + ): PendingIntent { + return PendingIntent.getBroadcast(this, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) + } + + private fun b64Decode(encodedString: String): ByteArray { + return byteArrayOf() + } + + private fun convertPrivateKey(privateKeyBytes: ByteArray): ECPrivateKey { + return ECPrivateKeyImpl() + } +} + +object CredentialsRepo { + const val EXTRA_KEY_ACCOUNT_ID: String = "" +} + +class MyDatabase { + fun addNewPassword(passwordInfo: PasswordInfo) {} + + fun getPasskey(credIdEnc: String): PasskeyInfo { + return PasskeyInfo() + } + + fun getCredentials(packageName: String): CredentialsInfo { + return CredentialsInfo() + } +} + +data class PasswordInfo( + val id: String = "", + val password: String = "", + val packageName: String = "", + val username: String = "" +) + +data class PasskeyInfo( + val credPrivateKey: String = "", + val uid: String = "", + val username: String = "" +) + +data class CredentialsInfo( + val passwords: List = listOf() +) + +class ECPrivateKeyImpl: ECPrivateKey { + override fun getAlgorithm(): String = "" + override fun getFormat(): String = "" + override fun getEncoded(): ByteArray = byteArrayOf() + override fun getParams(): ECParameterSpec { + return ECParameterSpec( + EllipticCurve( + { 0 }, + BigInteger.ZERO, + BigInteger.ZERO + ), + ECPoint( + BigInteger.ZERO, + BigInteger.ZERO + ), + BigInteger.ZERO, + 0 + ) + } + override fun getS(): BigInteger = BigInteger.ZERO +} diff --git a/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt new file mode 100644 index 00000000..50beedae --- /dev/null +++ b/identity/credentialmanager/src/main/java/com/example/identity/credentialmanager/MyCredentialProviderService.kt @@ -0,0 +1,304 @@ +package com.example.identity.credentialmanager + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Intent +import android.graphics.drawable.Icon +import android.os.Build.VERSION_CODES +import android.os.Bundle +import android.os.CancellationSignal +import android.os.OutcomeReceiver +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.credentials.exceptions.ClearCredentialException +import androidx.credentials.exceptions.CreateCredentialException +import androidx.credentials.exceptions.CreateCredentialUnknownException +import androidx.credentials.exceptions.GetCredentialException +import androidx.credentials.exceptions.GetCredentialUnknownException +import androidx.credentials.provider.AuthenticationAction +import androidx.credentials.provider.BeginCreateCredentialRequest +import androidx.credentials.provider.BeginCreateCredentialResponse +import androidx.credentials.provider.BeginCreatePublicKeyCredentialRequest +import androidx.credentials.provider.BeginGetCredentialRequest +import androidx.credentials.provider.BeginGetCredentialResponse +import androidx.credentials.provider.BeginGetPasswordOption +import androidx.credentials.provider.BeginGetPublicKeyCredentialOption +import androidx.credentials.provider.CallingAppInfo +import androidx.credentials.provider.CreateEntry +import androidx.credentials.provider.CredentialEntry +import androidx.credentials.provider.CredentialProviderService +import androidx.credentials.provider.PasswordCredentialEntry +import androidx.credentials.provider.ProviderClearCredentialStateRequest +import androidx.credentials.provider.PublicKeyCredentialEntry +import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions + +@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE) +class MyCredentialProviderService: CredentialProviderService() { + private val PERSONAL_ACCOUNT_ID: String = "" + private val FAMILY_ACCOUNT_ID: String = "" + private val CREATE_PASSKEY_INTENT: String = "" + private val PACKAGE_NAME: String = "" + private val EXTRA_KEY_ACCOUNT_ID: String = "" + private val UNIQUE_REQ_CODE: Int = 1 + private val UNLOCK_INTENT: String = "" + private val UNIQUE_REQUEST_CODE: Int = 0 + private val TAG: String = "" + private val GET_PASSWORD_INTENT: String = "" + + // [START android_identity_credential_provider_passkey_creation] + override fun onBeginCreateCredentialRequest( + request: BeginCreateCredentialRequest, + cancellationSignal: CancellationSignal, + callback: OutcomeReceiver, + ) { + val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request) + if (response != null) { + callback.onResult(response) + } else { + callback.onError(CreateCredentialUnknownException()) + } + } + + fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? { + when (request) { + is BeginCreatePublicKeyCredentialRequest -> { + // Request is passkey type + return handleCreatePasskeyQuery(request) + } + } + // Request not supported + return null + } + + private fun handleCreatePasskeyQuery( + request: BeginCreatePublicKeyCredentialRequest + ): BeginCreateCredentialResponse { + + // Adding two create entries - one for storing credentials to the 'Personal' + // account, and one for storing them to the 'Family' account. These + // accounts are local to this sample app only. + val createEntries: MutableList = mutableListOf() + createEntries.add( CreateEntry( + PERSONAL_ACCOUNT_ID, + createNewPendingIntent(PERSONAL_ACCOUNT_ID, CREATE_PASSKEY_INTENT) + )) + + createEntries.add( CreateEntry( + FAMILY_ACCOUNT_ID, + createNewPendingIntent(FAMILY_ACCOUNT_ID, CREATE_PASSKEY_INTENT) + )) + + return BeginCreateCredentialResponse(createEntries) + } + + private fun createNewPendingIntent(accountId: String, action: String): PendingIntent { + val intent = Intent(action).setPackage(PACKAGE_NAME) + + // Add your local account ID as an extra to the intent, so that when + // user selects this entry, the credential can be saved to this + // account + intent.putExtra(EXTRA_KEY_ACCOUNT_ID, accountId) + + return PendingIntent.getActivity( + applicationContext, UNIQUE_REQ_CODE, + intent, ( + PendingIntent.FLAG_MUTABLE + or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } + // [END android_identity_credential_provider_passkey_creation] + + private lateinit var response: BeginGetCredentialResponse + + // [START android_identity_credential_provider_sign_in] + private val unlockEntryTitle = "Authenticate to continue" + + override fun onBeginGetCredentialRequest( + request: BeginGetCredentialRequest, + cancellationSignal: CancellationSignal, + callback: OutcomeReceiver, + ) { + if (isAppLocked()) { + callback.onResult(BeginGetCredentialResponse( + authenticationActions = mutableListOf( + AuthenticationAction( + unlockEntryTitle, createUnlockPendingIntent()) + ) + ) + ) + return + } + try { + response = processGetCredentialRequest(request) + callback.onResult(response) + } catch (e: GetCredentialException) { + callback.onError(GetCredentialUnknownException()) + } + } + // [END android_identity_credential_provider_sign_in] + + // [START android_identity_credential_provider_unlock_pending_intent] + private fun createUnlockPendingIntent(): PendingIntent { + val intent = Intent(UNLOCK_INTENT).setPackage(PACKAGE_NAME) + return PendingIntent.getActivity( + applicationContext, UNIQUE_REQUEST_CODE, intent, ( + PendingIntent.FLAG_MUTABLE + or PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + } + // [END android_identity_credential_provider_unlock_pending_intent] + + // [START android_identity_credential_provider_process_get_credential_request] + companion object { + // These intent actions are specified for corresponding activities + // that are to be invoked through the PendingIntent(s) + private const val GET_PASSKEY_INTENT_ACTION = "PACKAGE_NAME.GET_PASSKEY" + private const val GET_PASSWORD_INTENT_ACTION = "PACKAGE_NAME.GET_PASSWORD" + + } + + fun processGetCredentialRequest( + request: BeginGetCredentialRequest + ): BeginGetCredentialResponse { + val callingPackageInfo = request.callingAppInfo + val callingPackageName = callingPackageInfo?.packageName.orEmpty() + val credentialEntries: MutableList = mutableListOf() + + for (option in request.beginGetCredentialOptions) { + when (option) { + is BeginGetPasswordOption -> { + credentialEntries.addAll( + populatePasswordData( + callingPackageName, + option + ) + ) + } + is BeginGetPublicKeyCredentialOption -> { + credentialEntries.addAll( + populatePasskeyData( + callingPackageInfo, + option + ) + ) + } else -> { + Log.i(TAG, "Request not supported") + } + } + } + return BeginGetCredentialResponse(credentialEntries) + } + // [END android_identity_credential_provider_process_get_credential_request] + + @SuppressLint("RestrictedApi") + // [START android_identity_credential_provider_populate_pkpw_data] + private fun populatePasskeyData( + callingAppInfo: CallingAppInfo?, + option: BeginGetPublicKeyCredentialOption + ): List { + val passkeyEntries: MutableList = mutableListOf() + val request = PublicKeyCredentialRequestOptions(option.requestJson) + // Get your credentials from database where you saved during creation flow + val creds = getCredentialsFromInternalDb(request.rpId) + val passkeys = creds.passkeys + for (passkey in passkeys) { + val data = Bundle() + data.putString("credId", passkey.credId) + passkeyEntries.add( + PublicKeyCredentialEntry( + context = applicationContext, + username = passkey.username, + pendingIntent = createNewPendingIntent( + GET_PASSKEY_INTENT_ACTION, + data + ), + beginGetPublicKeyCredentialOption = option, + displayName = passkey.displayName, + icon = passkey.icon + ) + ) + } + return passkeyEntries + } + + // Fetch password credentials and create password entries to populate to the user + private fun populatePasswordData( + callingPackage: String, + option: BeginGetPasswordOption + ): List { + val passwordEntries: MutableList = mutableListOf() + + // Get your password credentials from database where you saved during + // creation flow + val creds = getCredentialsFromInternalDb(callingPackage) + val passwords = creds.passwords + for (password in passwords) { + passwordEntries.add( + PasswordCredentialEntry( + context = applicationContext, + username = password.username, + pendingIntent = createNewPendingIntent( + GET_PASSWORD_INTENT + ), + beginGetPasswordOption = option, + displayName = password.username, + icon = password.icon + ) + ) + } + return passwordEntries + } + + private fun createNewPendingIntent( + action: String, + extra: Bundle? = null + ): PendingIntent { + val intent = Intent(action).setPackage(PACKAGE_NAME) + if (extra != null) { + intent.putExtra("CREDENTIAL_DATA", extra) + } + + return PendingIntent.getActivity( + applicationContext, UNIQUE_REQUEST_CODE, intent, + (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + ) + } + // [END android_identity_credential_provider_populate_pkpw_data] + + // [START android_identity_credential_provider_clear_credential] + override fun onClearCredentialStateRequest( + request: ProviderClearCredentialStateRequest, + cancellationSignal: CancellationSignal, + callback: OutcomeReceiver + ) { + // Delete any maintained state as appropriate. + } + // [END android_identity_credential_provider_clear_credential] + + private fun isAppLocked(): Boolean { + return true + } + + private fun getCredentialsFromInternalDb(rpId: String): Creds { + return Creds() + } +} + +data class Creds( + val passkeys: List = listOf(), + val passwords: List = listOf() +) + +data class Passkey( + val credId: String = "", + val username: String = "", + val displayName: String = "", + val icon: Icon +) + +data class Password( + val username: String = "", + val icon: Icon +) diff --git a/identity/credentialmanager/src/main/res/xml/provider.xml b/identity/credentialmanager/src/main/res/xml/provider.xml new file mode 100644 index 00000000..81bdbd01 --- /dev/null +++ b/identity/credentialmanager/src/main/res/xml/provider.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/identity/credentialmanager/src/main/res/xml/provider_settings.xml b/identity/credentialmanager/src/main/res/xml/provider_settings.xml new file mode 100644 index 00000000..698ba5f6 --- /dev/null +++ b/identity/credentialmanager/src/main/res/xml/provider_settings.xml @@ -0,0 +1,11 @@ + + + + + + + +