ক্রেডেনশিয়াল ট্রান্সফার

ক্রেডেনশিয়াল ম্যানেজারের ক্রেডেনশিয়াল ট্রান্সফার API, ক্রেডেনশিয়াল প্রদানকারীদের মধ্যে ব্যবহারকারীর ক্রেডেনশিয়াল একই ডিভাইসে নিরাপদে ট্রান্সফার করার সুবিধা দেয়। এই নির্দেশিকা থেকে বিস্তারিতভাবে জানা যাবে কীভাবে Android-এ ক্রেডেনশিয়াল প্রদানকারীরা androidx.credentials:providerevents লাইব্রেরির মাধ্যমে প্রদান করা API-এর সাথে ইন্টিগ্রেট করতে পারে। এই ফিচার পাসওয়ার্ড, পাসকী, ঠিকানা সংক্রান্ত তথ্য এবং কাস্টম ফিল্ডে কাজ করে। এক্ষেত্রে স্ট্যান্ডার্ডাইজড FIDO ক্রেডেনশিয়াল এক্সচেঞ্জ ফর্ম্যাট (CXF) ব্যবহার করা হয়।

মূল ধারণা

ক্রেডেনশিয়াল ট্রান্সফার ফ্রেমওয়ার্ক, একই ডিভাইসে পিয়ার-টু-পিয়ার ক্রেডেনশিয়াল ট্রান্সফার করার সুবিধা প্রদান করে। এর ফলে, Android OS বা যাচাই না করা অ্যাপের কাছে র-ক্রেডেনশিয়াল প্রকাশ করতে হয় না।

ফ্রেমওয়ার্ক দুটি প্রাথমিক ভূমিকা সংজ্ঞায়িত করে:

  • এক্সপোর্টার (সোর্স প্রোভাইডার): ক্রেডেনশিয়াল প্রোভাইডার যার কাছে বর্তমানে ব্যবহারকারীর ক্রেডেনশিয়াল রয়েছে। এটি সিস্টেমের সাথে উপলভ্য এক্সপোর্ট করা যায় এমন অ্যাকাউন্টের (ExportEntry) মেটাডেটা প্রি-রেজিস্টার করে এবং ব্যবহারকারী বেছে নিলে ট্রান্সফারের অনুরোধে সাড়া দেয়।
  • ইম্পোর্টার (ক্লায়েন্ট প্রোভাইডার বা সেট-আপ উইজার্ড): ক্রেডেনশিয়াল প্রোভাইডার বা সেট-আপ উইজার্ড যা ইম্পোর্ট করার অনুরোধ শুরু করে (ImportCredentialsRequest) এবং এটি কী ধরনের ক্রেডেনশিয়াল ও এক্সটেনশন গ্রহণ করতে পারে তা নির্দিষ্ট করে।

Android ভার্সন কম্প্যাটিবিলিটি

Credentials Transfer API, Android 8 (API লেভেল ২৬) এবং তার পরবর্তী যেকোনও ভার্সনে কাজ করে।

ডিপেন্ডেন্সি যোগ করুন

আপনার মডিউলের build.gradle বা build.gradle.kts-এ androidx.credentials:providerevents ডিপেন্ডেন্সি যোগ করুন:

dependencies {
    implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}

প্রয়োজনীয় ক্লাস ইনস্ট্যান্টিয়েট করুন

প্রয়োজনীয় ProviderEventsManager-এর ইনস্ট্যান্স তৈরি করুন।

val providerEventsManager = ProviderEventsManager.create(context)

এক্সপোর্টার প্রয়োগ করা

ব্যবহারকারীদের আপনার অ্যাপ থেকে ডিভাইসের অন্যান্য ক্রেডেনশিয়াল প্রোভাইডারে ক্রেডেনশিয়াল এক্সপোর্ট করার অনুমতি দিতে, এক্সপোর্টার রোল প্রয়োগ করুন।

এক্সপোর্ট এন্ট্রি রেজিস্টার করুন

আপনার ক্রেডেনশিয়াল প্রোভাইডার পরিবর্তন হলে (যেমন, কোনও ব্যবহারকারী সাইন-ইন করলে, ক্রেডেনশিয়াল যোগ করলে বা অ্যাকাউন্ট পরিবর্তন করলে), ProviderEventsManager.registerExport() ব্যবহার করে আপনার ExportEntry আইটেম রেজিস্টার বা আপডেট করুন।

প্রতিটি ExportEntry-এর জন্য এগুলি প্রয়োজন:

  • id: একটি গোপন, র‌্যান্ডমভাবে জেনারেট করা স্ট্রিং শনাক্তকারী যা অনন্যভাবে এই এক্সপোর্ট এন্ট্রিকে উপস্থাপন করে। আপনাকে অবশ্যই এই আইডিটি নিরাপদে রাখতে হবে; ইনকামিং ট্রান্সফারের অনুরোধ যাচাই করার জন্য পরে আপনার এটির প্রয়োজন হবে।
  • accountDisplayName: ঐচ্ছিক অ্যাকাউন্ট লেবেল (যেমন, "Personal Account")।
  • userDisplayName: ব্যবহারকারীর প্রাথমিক শনাক্তকারী (যেমন, "alice@example.com")।
  • icon: একটি Bitmap আইকন যা পরিষেবা প্রদানকারী বা অ্যাকাউন্টকে উপস্থাপন করে (লাইব্রেরি দ্বারা স্বয়ংক্রিয়ভাবে 32x32 PNG-তে স্কেল করা হয়)।
  • supportedCredentialTypes: CredentialTypes থেকে স্ট্রিং কনস্ট্যান্টের একটি সেট যা এই এন্ট্রিতে কী ধরনের ডেটা আছে তা দেখায়।

suspend fun registerMyProviderForExport(
    providerEventsManager: ProviderEventsManager,
    providerIcon: Bitmap,
    // Randomly generated and stored in encrypted storage
    secretEntryId: String
) {
    val entry = ExportEntry(
        id = secretEntryId,
        accountDisplayName = "MyProvider Personal",
        userDisplayName = "alice@example.com",
        icon = providerIcon,
        supportedCredentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD
        )
    )

    // RegisterExportRequest.create() attaches the default WASM matcher from assets
    val request = RegisterExportRequest.create(context, listOf(entry))

    try {
        val response = providerEventsManager.registerExport(request)
        // Registration successful
    } catch (e: Exception) {
        // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException)
    }
}

ম্যানিফেস্ট ফাইলে এক্সপোর্টার অ্যাক্টিভিটি ঘোষণা করুন

ব্যবহারকারী সিস্টেম সিলেক্টর UI-তে আপনার ExportEntry বেছে নিলে, সিস্টেম আপনার নির্দিষ্ট করা হ্যান্ডলিং Activity চালু করে। বাধ্যতামূলক ইনটেন্ট অ্যাকশন ও কন্টেন্ট URI স্কিমের সাথে এই অ্যাক্টিভিটি ঘোষণা করুন।

অননুমোদিত ইনভোকেশন এবং কনফিউজড-ডেপুটি অ্যাটাক থেকে সুরক্ষিত রাখতে, Android 14 (API লেভেল 34) এবং এর পরবর্তী যেকোনও ভার্সনে android:requireContentUriPermissionFromCaller="write" ডিক্লেয়ার করুন। এটি Android অপারেটিং সিস্টেমকে নির্দেশ দেয় যাতে অ্যাক্টিভিটি শুরু করার অনুমতি দেওয়ার আগে কলারের কাছে ডেস্টিনেশন কন্টেন্ট URI-তে রাইট পার্মিশন আছে কিনা তা যাচাই করা হয়, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    android:requireContentUriPermissionFromCaller="write"
    android:label="@string/export_activity_label">
    <intent-filter>
        <!-- This intent action is required for Credential Manager to invoke this activity -->
        <action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

আপনার অ্যাক্টিভিটিতে ট্রান্সফার ইনটেন্ট ম্যানেজ করুন

আপনার CredentialExportActivity-এ, অনুরোধ পার্স করতে, কলিং কম্পোনেন্ট ও অনুরোধের মেটাডেটা যাচাই করতে, প্রয়োজনীয় বায়োমেট্রিক যাচাইকরণ করতে এবং প্রদান করা কন্টেন্ট URI-তে FIDO CXF পেলোড লিখতে IntentHandler.retrieveProviderImportCredentialsRequest(intent) ব্যবহার করুন।

অনুরোধ যাচাই করা

আপনার অ্যাক্টিভিটি এক্সপোর্ট করা হয় বলে, ক্রেডেনশিয়াল এক্সপোর্ট ফ্লো সুরক্ষিত করতে নিম্নলিখিত প্রতিটি লেয়ার যাচাই করুন:

  1. credId সেশন ননস (প্রাথমিক সুরক্ষা) যাচাই করুন: আপনার ExportEntry রেজিস্টার করার সময়, ক্রিপ্টোগ্রাফিক্যালি র‍্যান্ডম, হাই-এনট্রপি id (ননস) জেনারেট করুন এবং এটিকে স্বল্প-মেয়াদী সেশনের সাথে যুক্ত করুন। আপনার অ্যাক্টিভিটিতে, যাচাই করুন যে request.credId আপনার পরিষেবা প্রদানকারীর আগে ইস্যু করা অ্যাক্টিভ, মেয়াদ শেষ না হওয়া সেশন ননসের সাথে ম্যাচ করছে। থার্ড-পার্টি অ্যাপ এই ননস অনুমান করতে পারে না বলে, এটি নিশ্চিত করে যে ইনটেন্ট সিস্টেম সিলেক্টরে প্রমাণিত ব্যবহারকারীর পছন্দের সাথে মিলে যায়।
  2. Activity.getCallingPackage() যাচাই করুন (কলার চেক): যেহেতু ক্রেডেনশিয়াল সিলেক্টর startActivityForResult ব্যবহার করে আপনার অ্যাক্টিভিটি লঞ্চ করে, তাই আপনার অ্যাক্টিভিটি getCallingPackage() চেক করে নিশ্চিত করতে পারে যে UI লঞ্চ করা ইমিডিয়েট কলার হল বিশ্বস্ত সিস্টেম ক্রেডেনশিয়াল সিলেক্টর ফ্রেমওয়ার্ক (Google Play পরিষেবা সহ Android ডিভাইসে, "com.google.android.gms")। কলিং প্যাকেজটি অপরিচিত হলে অনুরোধটি প্রত্যাখ্যান করুন।
  3. request.callingAppInfo (টার্গেট অ্যাপের তথ্য) পরীক্ষা করুন: callingAppInfo ইমপোর্ট করা ক্রেডেনশিয়াল পাবে এমন ডেস্টিনেশন অ্যাপ্লিকেশন শনাক্ত করে। আপনি এই তথ্যটি স্বচ্ছতার জন্য উপযুক্ত বলে মনে করলে ব্যবহার করতে পারেন (যেমন আপনার কনফার্মেশন UI-তে ডেস্টিনেশন অ্যাপের নাম এবং আইকন দেখানো) বা ডেস্টিনেশন অ্যাপ্লিকেশনটি বিশ্বস্ত কিনা তা যাচাই করতে পারেন।

class CredentialExportActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Extract the transfer request from the incoming Intent
        val request: ProviderImportCredentialsRequest? =
            IntentHandler.retrieveProviderImportCredentialsRequest(intent)

        if (request == null) {
            finishWithError()
            return
        }

        // 2. Validate CallingAppInfo and secret `credId`
        val callingAppPackage = request.callingAppInfo.packageName
        val receivedCredId = request.credId
        if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) {
            // Secret ID mismatch or untrusted caller -> abort
            sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request"))
            return
        }

        // 3. Optional: Prompt user for Biometric / PIN authentication before exporting
        authenticateUserThenExport(request)
    }

    private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) {
        // ... Biometric prompt logic ...
        // Once authenticated, generate the FIDO CXF JSON string matching the requested types
        val cxfJsonPayload = buildFidoCxfJsonPayload(
            requestedTypes = request.request.credentialTypes,
            requestedExtensions = request.request.knownExtensions
        )

        val response = ImportCredentialsResponse(cxfJsonPayload)

        // 4. Write the JSON payload to the Content URI and set Activity result
        IntentHandler.setImportCredentialsResponse(
            context = this,
            uri = request.uri,
            intent = intent,
            response = response
        )
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) {
        IntentHandler.setImportCredentialsException(intent, exception)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun verifySecretEntryId(credentialId: String): Boolean {
        // Check if credentialId matches what you stored when calling RegisterExportRequest
        return credentialId == getStoredSecretEntryId()
    }

    private fun isTrustedImporter(packageName: String): Boolean {
        // Implement any specific allowlisting / caller checks if required
        return true
    }

    private fun finishWithError() {
        setResult(Activity.RESULT_CANCELED)
        finish()
    }
}

রেজিস্ট্রেশন এক্সপোর্ট করা এবং ব্যতিক্রমগুলি মুছে ফেলা

এই মডিউলের সব ব্যতিক্রম হল ImportCredentialsException, RegisterExportException অথবা ClearExportException-এর সাবক্লাস।

  • RegisterExportProviderConfigurationException অথবা ClearExportProviderConfigurationException: পরিষেবা প্রদানকারীর সেট-আপ সংক্রান্ত সমস্যার কারণে (যেমন, ExportEntry-এ খালি supportedCredentialTypes অথবা কাজ করে না এমন OS টিয়ারে কল করা) রেজিস্ট্রেশন অথবা মুছে দেওয়ার কাজ সম্পূর্ণ না হলে এটি থ্রো করা হয়।
  • RegisterExportUnknownErrorException অথবা ClearExportUnknownErrorException: এক্সপোর্ট রেজিস্ট্রি আপডেট করার সময় শ্রেণীবিন্যাস না করা সিস্টেম বা স্টোরেজ সংক্রান্ত সমস্যা হয়েছে।

ইমপোর্টার প্রয়োগ করুন

আপনার অ্যাপে ক্রেডেনশিয়াল ইমপোর্ট করতে (যেমন, অনবোর্ডিং বা পরিষেবা প্রদানকারী ইমপোর্ট করার সময়), ইমপোর্টার রোল প্রয়োগ করুন এবং ProviderEventsManager.importCredentials() কল করে ফ্লো শুরু করুন।

ইমপোর্ট করার অনুরোধ তৈরি করুন এবং ফ্লো চালু করুন

আপনার ইমপোর্টার কোন CredentialTypes এবং KnownExtensions সাপোর্ট করে তা উল্লেখ করুন:

suspend fun startCredentialImport(
    activityContext: Context,
    providerEventsManager: ProviderEventsManager
) {
    val importRequest = ImportCredentialsRequest(
        credentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_NOTE
        ),
        knownExtensions = setOf(
            KnownExtensions.KNOWN_EXTENSION_SHARED
        )
    )

    try {
        // Launches the system Selector UI; suspends until user selects a provider and completes transfer
        val response = providerEventsManager.importCredentials(activityContext, importRequest)

        // 1. Inspect the source exporter's package info
        val exporterPackageName = response.callingAppInfo.packageName

        // 2. Parse the FIDO CXF JSON string
        val cxfJsonString = response.response.responseJson
        parseAndSaveImportedCredentials(cxfJsonString)
    } catch (e: ImportCredentialsException) {
        // Handle specific import exceptions (e.g., ImportCredentialsCancellationException)
        handleImportFailure(e)
    }
}

private fun parseAndSaveImportedCredentials(cxfJsonString: String) {
    val rootJson = JSONObject(cxfJsonString)
    // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification:
    // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html
}

// Helper function to make it compile
private fun handleImportFailure(e: ImportCredentialsException) {}

ইমপোর্ট ফ্লো সংক্রান্ত ব্যতিক্রম

এই মডিউলের সব ব্যতিক্রম হল ImportCredentialsException, RegisterExportException অথবা ClearExportException-এর সাবক্লাস।

  • ImportCredentialsCancellationException: ব্যবহারকারী নির্বাচক UI বাতিল করেছেন বা এক্সপোর্ট অ্যাক্টিভিটি (Activity.RESULT_CANCELED) বাতিল করেছেন।
  • ImportCredentialsNoExportOptionException: অনুরোধ করা ক্রেডেনশিয়ালের ধরনের সাথে কোনও রেজিস্টার করা এক্সপোর্ট এন্ট্রি মেলেনি অথবা বেছে নেওয়া এক্সপোর্টার প্রত্যাখ্যানের ব্যতিক্রম দেখিয়েছে।
  • ImportCredentialsProviderConfigurationException: কনফিগারেশন সংক্রান্ত সমস্যা (যেমন, ImportCredentialsRequest-এ ফাঁকা credentialTypes সেট করা অথবা অনুমতি না থাকা)।
  • ImportCredentialsInvalidJsonException: এক্সপোর্টার একটি ত্রুটিপূর্ণ বা খালি JSON পেলোড ফিরিয়ে দিয়েছে যা অনুরোধ যাচাইকরণে ব্যর্থ হয়েছে।
  • ImportCredentialsSystemErrorException: ইম্পোর্ট করার সময় ইন্টার্নাল Android সিস্টেম বা বাইন্ডার ট্রান্সফার সংক্রান্ত সমস্যা হয়েছে।
  • ImportCredentialsUnknownCallerException: ফ্রেমওয়ার্কের মাধ্যমে কলিং অ্যাপ যাচাই করা যায়নি।
  • ImportCredentialsUnknownErrorException: ইমপোর্ট ফ্লো চলাকালীন শ্রেণীবিন্যাস না করা বা অপ্রত্যাশিত সমস্যা।

কাজ করে এমন ক্রেডেনশিয়ালের ধরন ও এক্সটেনশন

androidx.credentials.providerevents.transfer.CredentialTypes অবজেক্ট FIDO CXF আইটেমের ধরন অনুযায়ী স্ট্যান্ডার্ডাইজড স্ট্রিং কনস্ট্যান্ট নির্ধারণ করে:

স্থির মান (cxf ধরন) বর্ণনা
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" ইউজারনেম ও পাসওয়ার্ড লগ-ইন ক্রেডেনশিয়াল।
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" FIDO2 বা WebAuthn পাসকী পাবলিক কী ক্রেডেনশিয়াল।
CREDENTIAL_TYPE_ADDRESS "address" ফর্ম অটোফিল করার জন্য পোস্টাল বা শিপিং অ্যাড্রেস সংক্রান্ত তথ্য।
CREDENTIAL_TYPE_API_KEY "api-key" API অ্যাক্সেস কী ও টোকেন।
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" ক্রেডিট ও ডেবিট কার্ড পেমেন্ট সংক্রান্ত তথ্য।
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" কাস্টম গ্রুপিং বা ব্যবহারকারীর দ্বারা নির্ধারিত ফিল্ড।
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" ড্রাইভিং লাইসেন্সের বিবরণ।
CREDENTIAL_TYPE_FILE "file" বাইনারি ফাইলের জন্য মেটাডেটা ও প্লেসহোল্ডার রেফারেন্স।
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" মেশিন-জেনারেটেড সুরক্ষিত পাসওয়ার্ড।
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" জাতীয় আইডি কার্ড, SSN, TIN বা পাসপোর্ট রেফারেন্স।
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" পে-লোডে অন্য আইটেমকে নির্দেশ করে এমন লজিক্যাল লিঙ্ক।
CREDENTIAL_TYPE_NOTE "note" ব্যবহারকারীর দ্বারা নির্ধারিত সুরক্ষিত নোট (UTF-৮ স্ট্রিং)।
CREDENTIAL_TYPE_PASSPORT "passport" পাসপোর্ট ট্রাভেল ডকুমেন্টের বিবরণ।
CREDENTIAL_TYPE_PERSON_NAME "person-name" ব্যক্তির পরিচয় ও নাম সংক্রান্ত বিবরণ।
CREDENTIAL_TYPE_SSH_KEY "ssh-key" SSH পাবলিক ও প্রাইভেট কী পেয়ার।
CREDENTIAL_TYPE_TOTP "totp" সময়-ভিত্তিক একবার ব্যবহারের পাসওয়ার্ড (2FA) সংক্রান্ত গোপন তথ্য।
CREDENTIAL_TYPE_WIFI "wifi" ওয়াই-ফাই নেটওয়ার্ক SSID ও পাসফ্রেজ।

কাস্টম WASM (WebAssembly) ম্যাচিং (উন্নত)

ডিফল্ট হিসেবে, RegisterExportRequest.create(context, entries) কল করলে লাইব্রেরি অ্যাসেট থেকে সিস্টেম-ডিফল্ট credential_transfer_matcher.wasm বান্ডেল করে, যা supportedCredentialTypes-এর ইন্টারসেকশনের উপর ভিত্তি করে এন্ট্রি ফিল্টার করে।

আপনার ক্রেডেনশিয়াল প্রদানকারীর জটিল ম্যাচিং লজিকের (যেমন, ডাইনামিক ক্ষমতা চেক করা বা কাস্টম ফিল্ডের উপর ভিত্তি করে শর্তসাপেক্ষ ফিল্টারিং) প্রয়োজন হলে, আপনি WASM ক্রেডেনশিয়াল ট্রান্সফার API-এর সাথে ম্যাচ করে এমন আপনার নিজস্ব WebAssembly মডিউল কম্পাইল করতে এবং সরাসরি র বাইট অ্যারে পাস করতে পারবেন:

// Loading a custom WASM matcher
val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() }
val customRequest = RegisterExportRequest(
    entries = myEntries,
    exportMatcher = customMatcherBytes
)
providerEventsManager.registerExport(customRequest)