Implement Rust-owned adaptive Android password navigation (#96)
Android ARM verification / rust (push) Failing after 46m4s
Android ARM verification / android (push) Failing after 9s
Dependency security audit / rustsec (push) Successful in 22s

This commit is contained in:
hugo committed 2026-10-09 12:25:02 +02:00
1 parent 36e5f55283
commit 8c9341f214
26 files changed
+1553 -16

No files matched your search

@@ -1,6 +1,9 @@
package de.rfc1437.ironstorage.bridge
import androidx.test.platform.app.InstrumentationRegistry
import de.rfc1437.ironstorage.bridge.generated.MobileAuthenticationFfiException
import de.rfc1437.ironstorage.bridge.generated.MobileClock
import de.rfc1437.ironstorage.bridge.generated.MobileEntryEditorInput
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingErrorKind
import de.rfc1437.ironstorage.bridge.generated.MobileOnboardingFfiException
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
@@ -18,6 +21,131 @@ import org.junit.Assume.assumeTrue
import org.junit.Test
class AndroidOnboardingTest {
@Test fun privateNavigationTraversesWithoutAuthenticationAndRejectsReplacedEntries() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val id = "navigation-${UUID.randomUUID()}"
val directories = MobilePrivateDirectories(
File(context.filesDir, id).apply { mkdirs() }.canonicalPath,
File(context.noBackupFilesDir, id).apply { mkdirs() }.canonicalPath,
File(context.cacheDir, id).apply { mkdirs() }.canonicalPath
)
val backend = MemoryBackend()
val native = NativeRuntime(context)
native.privateStorage(directories).use { runtime ->
runtime.androidOnboarding(backend).use { setup ->
val key =
requireNotNull(
setup.generate("Navigation fixture", "public-test-passphrase").selectedKey
)
setup.setupLocal(key, "public-test-passphrase")
}
val clock = object : MobileClock {
override fun monotonicMilliseconds(): ULong =
android.os.SystemClock.elapsedRealtime().toULong()
}
runtime.authentication(backend, clock).use { authentication ->
authentication.createDirectory("", "仕事")
authentication.createDirectory("仕事", "銀行")
authentication.createDirectory("仕事/銀行", "Empty")
val draft = authentication.beginCreateEntry(
"仕事/銀行",
"Long Unicode 密碼",
"public-test-passphrase"
)
authentication.updateEntryEditor(
draft.id,
listOf(
MobileEntryEditorInput(draft.page.fields[0].id, null, "fixture-password")
)
)
authentication.saveEntryEditor(
draft.id,
listOf(
MobileEntryEditorInput(draft.page.fields[0].id, null, "fixture-password")
)
)
authentication.manualLock()
val root = runtime.passwordNavigation("", null, null, false)
val folder = runtime.passwordNavigation(
"",
root.rows[0].row.id,
root.revision,
true
)
assertEquals("仕事", folder.path)
val bank = runtime.passwordNavigation("仕事/銀行", null, null, false)
val row = bank.rows.single { it.row.title == "Long Unicode 密碼" }
val selected = runtime.passwordNavigation(
bank.path,
row.row.id,
bank.revision,
true
)
assertEquals(row.row.id, selected.selected?.row?.id)
assertFalse(authentication.state().unlocked)
val presentation = authentication.navigationEntry(
bank.path,
row.row.id,
bank.revision,
"public-test-passphrase",
0u
)
assertTrue(presentation.page.sections.isNotEmpty())
assertTrue(
presentation.page.sections.flatMap { it.fields }.none {
it.value ==
"fixture-password"
}
)
authentication.manualLock()
assertTrue(
runtime.passwordNavigation("仕事/銀行/Empty", null, null, false).rows.isEmpty()
)
native.privateStorage(directories).use { recreated ->
assertEquals(
row.row.id,
recreated.passwordNavigation(
bank.path,
row.row.id,
bank.revision,
false
).selected?.row?.id
)
}
authentication.unlockEntry(row.row.path, "public-test-passphrase")
val editor = authentication.beginEntryEditor(row.row.path)
authentication.updateEntryEditor(
editor.id,
listOf(
MobileEntryEditorInput(editor.page.fields[0].id, null, "changed-password")
)
)
authentication.saveEntryEditor(
editor.id,
listOf(
MobileEntryEditorInput(editor.page.fields[0].id, null, "changed-password")
)
)
authentication.manualLock()
val stale = runtime.passwordNavigation(bank.path, row.row.id, bank.revision, true)
assertTrue(stale.stale)
assertEquals(null, stale.selected)
assertThrows(
MobileAuthenticationFfiException.Failed::class.java
) {
authentication.navigationEntry(
bank.path,
row.row.id,
bank.revision,
"public-test-passphrase",
0u
)
}
assertFalse(authentication.state().unlocked)
}
}
}
@Test fun realPublicHttpsDiscoveryAndConnectionUseThePlatformVerifier() {
assumeTrue(
InstrumentationRegistry.getArguments().getString("ironstorage_https_integration") ==