PasskeySelectionFlagSecureTest.kt

3.0 KB · raw

1package com.peachpasswords.passkeys23import android.app.Activity4import android.content.Intent5import android.view.WindowManager6import androidx.test.core.app.ActivityScenario7import androidx.test.ext.junit.runners.AndroidJUnit48import androidx.test.platform.app.InstrumentationRegistry9import androidx.test.runner.lifecycle.ActivityLifecycleCallback10import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry11import androidx.test.runner.lifecycle.Stage12import java.util.concurrent.CountDownLatch13import java.util.concurrent.TimeUnit14import java.util.concurrent.atomic.AtomicBoolean15import org.junit.Assert.assertTrue16import org.junit.Test17import org.junit.runner.RunWith1819/**20 * Real-device coverage for audit H-05.21 *22 * Passkey selection drives the WebAuthn assertion/registration flow against23 * vault-resident private keys, so the selection window must set FLAG_SECURE.24 * Debug builds are secure by default; only an explicit `qa_disable_secure_flag`25 * intent extra bypasses it.26 *27 * The activity auto-finishes on an empty test intent (completeSelection sets28 * an exception result and calls finish in onCreate), so we capture FLAG_SECURE29 * at CREATED via an ActivityLifecycleCallback rather than sampling onActivity.30 */31@RunWith(AndroidJUnit4::class)32class PasskeySelectionFlagSecureTest {33    @Test34    fun selectionWindowActuallyEnablesFlagSecure() {35        val context = InstrumentationRegistry.getInstrumentation().targetContext36        assertTrue(37            "target must be release or its correctly suffixed debug id",38            context.packageName == "com.peachpasswords" ||39                context.packageName == "com.peachpasswords.debug",40        )41        // Capture FLAG_SECURE at CREATED, before the activity may finish.42        val flagSecureObserved = AtomicBoolean(false)43        val createdLatch = CountDownLatch(1)44        val monitor = ActivityLifecycleMonitorRegistry.getInstance()45        val callback = ActivityLifecycleCallback { activity: Activity?, stage: Stage ->46            if (activity is PasskeyCredentialSelectionActivity && stage == Stage.CREATED) {47                flagSecureObserved.set(48                    activity.window.attributes.flags and WindowManager.LayoutParams.FLAG_SECURE != 0,49                )50                createdLatch.countDown()51            }52        }53        monitor.addLifecycleCallback(callback)54        try {55            val intent = Intent(context, PasskeyCredentialSelectionActivity::class.java)56            ActivityScenario.launch<PasskeyCredentialSelectionActivity>(intent).use { /* scenario closes automatically */ }57            assertTrue(58                "passkey selection activity did not reach CREATED within timeout",59                createdLatch.await(5, TimeUnit.SECONDS),60            )61            assertTrue(62                "passkey selection window must set FLAG_SECURE at creation",63                flagSecureObserved.get(),64            )65        } finally {66            monitor.removeLifecycleCallback(callback)67        }68    }69}