Implement Rust-owned Android Keystore secure storage (#93)
Dependency security audit / rustsec (push) Failing after 23s

This commit is contained in:
hugo committed 2026-10-08 16:22:57 +02:00
1 parent 874c8139a4
commit 1abd75282e
36 files changed
+4380 -37

No files matched your search

Generated
+1
View File
@@ -5390,6 +5390,7 @@ checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
name = "ironstorage"
version = "1.0.1"
dependencies = [
"aes-gcm 0.11.1",
"apple-native-keyring-store",
"arboard",
"cap-std",
+1
View File
@@ -53,6 +53,7 @@ dependencies {
implementation(project(":features"))
implementation(project(":core"))
implementation(project(":platform"))
implementation(libs.fragment)
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.material3)
+18 -8
View File
@@ -3,19 +3,24 @@
# This file is expected to be part of source control.
# To regenerate this file, run: ./gradlew :app:dependencies --write-locks
androidx.activity:activity-compose:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.activity:activity-ktx:1.13.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.activity:activity-ktx:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.activity:activity:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-experimental:1.5.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-jvm:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-jvm:1.9.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.annotation:annotation:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation:1.9.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.appcompat:appcompat-resources:1.2.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.appcompat:appcompat:1.2.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.autofill:autofill:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-jvm:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-ktx:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.biometric:biometric:1.1.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-jvm:1.5.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.collection:collection-jvm:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-ktx:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection:1.5.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.collection:collection:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.compose.animation:animation-android:1.12.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.compose.animation:animation-core-android:1.12.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.compose.animation:animation-core:1.12.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
@@ -62,10 +67,12 @@ androidx.concurrent:concurrent-futures:1.2.0=debugAndroidTestCompileClasspath,de
androidx.core:core-ktx:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core-viewtree:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core:1.18.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.customview:customview-poolingcontainer:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.drawerlayout:drawerlayout:1.0.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.emoji2:emoji2:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.fragment:fragment:1.5.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.fragment:fragment:1.9.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.graphics:graphics-path:1.0.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.interpolator:interpolator:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-common-java8:2.11.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
@@ -84,7 +91,7 @@ androidx.lifecycle:lifecycle-runtime:2.11.0=debugAndroidTestCompileClasspath,deb
androidx.lifecycle:lifecycle-viewmodel-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
@@ -101,7 +108,7 @@ androidx.profileinstaller:profileinstaller:1.4.0=debugAndroidTestLintChecksClass
androidx.savedstate:savedstate-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-compose-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-compose:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.startup:startup-runtime:1.1.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.test.espresso:espresso-core:3.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
@@ -111,8 +118,11 @@ androidx.test.services:storage:1.6.0=debugAndroidTestCompileClasspath,debugAndro
androidx.test:core:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:monitor:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:runner:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.tracing:tracing-android:2.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.tracing:tracing:1.1.0=debugAndroidTestCompileClasspath
androidx.tracing:tracing:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.tracing:tracing:2.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.window:window-core-android:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
+1
View File
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<application
android:name=".IronStorageApplication"
android:allowBackup="false"
@@ -1,24 +1,33 @@
package de.rfc1437.ironstorage
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.material3.MaterialTheme
import androidx.fragment.app.FragmentActivity
import dagger.hilt.android.AndroidEntryPoint
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.features.ShellViewModel
import de.rfc1437.ironstorage.platform.AndroidKeystoreAdapter
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
class MainActivity : FragmentActivity() {
@Inject lateinit var keystore: AndroidKeystoreAdapter
@Inject lateinit var repository: ShellRepository
private val viewModel: ShellViewModel by viewModels()
override fun onDestroy() {
keystore.detach(this)
super.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
keystore.attach(this)
enableEdgeToEdge()
setContent {
MaterialTheme {
@@ -8,6 +8,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import de.rfc1437.ironstorage.core.NativeFixtureShellRepository
import de.rfc1437.ironstorage.core.ShellRepository
import de.rfc1437.ironstorage.platform.AndroidKeystoreAdapter
import de.rfc1437.ironstorage.platform.privateDirectories
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
@@ -19,10 +20,18 @@ import kotlinx.coroutines.SupervisorJob
object ShellModule {
@Provides
@Singleton
fun shellRepository(@ApplicationContext context: Context): ShellRepository =
NativeFixtureShellRepository(
context,
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate),
{ privateDirectories(context) }
)
fun keystore(@ApplicationContext context: Context): AndroidKeystoreAdapter =
AndroidKeystoreAdapter(context)
@Provides
@Singleton
fun shellRepository(
@ApplicationContext context: Context,
keystore: AndroidKeystoreAdapter
): ShellRepository = NativeFixtureShellRepository(
context,
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate),
{ privateDirectories(context) },
keystore
)
}
@@ -0,0 +1,58 @@
package de.rfc1437.ironstorage.core
/** OS facts and cipher mechanics. Rust chooses policy, aliases and persistence. */
class KeystoreDeviceState(
val api: Int,
val locked: Boolean,
val secure: Boolean,
val strongBiometric: Boolean,
val strongbox: Boolean
)
enum class KeystoreAuthorization { None, StrongBiometric, UserPresence }
class KeystoreKeySpecification(
val alias: String,
val authorization: KeystoreAuthorization,
val unlockedDevice: Boolean,
val invalidateOnEnrollment: Boolean,
val strongbox: Boolean
)
class KeystoreCiphertext(val nonce: ByteArray, val bytes: ByteArray)
enum class KeystoreFailure {
MissingKey,
Invalidated,
Lockout,
Locked,
Denied,
Cancelled,
Corrupted,
UnsupportedProtection,
StrongboxUnavailable,
AlreadyExists,
Unavailable
}
class KeystoreException(val kind: KeystoreFailure) : Exception("Keystore operation failed")
interface AndroidKeystore {
fun deviceState(): KeystoreDeviceState
fun createKey(specification: KeystoreKeySpecification)
fun encrypt(
alias: String,
authorization: KeystoreAuthorization,
aad: ByteArray,
value: ByteArray
): KeystoreCiphertext
fun decrypt(
alias: String,
authorization: KeystoreAuthorization,
aad: ByteArray,
value: KeystoreCiphertext
): ByteArray
fun deleteKey(alias: String)
fun aliases(): List<String>
fun cancelPending()
}
@@ -0,0 +1,74 @@
package de.rfc1437.ironstorage.core
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidAuthorization
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidCiphertext
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidDeviceState
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeySpecification
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystore
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystoreException
internal class KeystoreCallback(private val os: AndroidKeystore) : MobileAndroidKeystore {
override fun deviceState(): MobileAndroidDeviceState = call {
val s = os.deviceState()
MobileAndroidDeviceState(s.api.toUInt(), s.locked, s.secure, s.strongBiometric, s.strongbox)
}
override fun createKey(specification: MobileAndroidKeySpecification) = call {
os.createKey(
KeystoreKeySpecification(
specification.alias,
specification.authorization.toOs(),
specification.unlockedDevice,
specification.invalidateOnEnrollment,
specification.strongbox
)
)
}
override fun encrypt(
alias: String,
authorization: MobileAndroidAuthorization,
aad: ByteArray,
value: ByteArray
): MobileAndroidCiphertext = call {
try {
val result = os.encrypt(alias, authorization.toOs(), aad, value)
MobileAndroidCiphertext(result.nonce, result.bytes)
} finally {
value.fill(0)
}
}
override fun decrypt(
alias: String,
authorization: MobileAndroidAuthorization,
aad: ByteArray,
value: MobileAndroidCiphertext
): ByteArray = call {
os.decrypt(alias, authorization.toOs(), aad, KeystoreCiphertext(value.nonce, value.bytes))
}
override fun deleteKey(alias: String) = call { os.deleteKey(alias) }
override fun aliases(): List<String> = call { os.aliases() }
override fun cancelPending() = os.cancelPending()
}
private fun MobileAndroidAuthorization.toOs(): KeystoreAuthorization = when (this) {
MobileAndroidAuthorization.NONE -> KeystoreAuthorization.None
MobileAndroidAuthorization.STRONG_BIOMETRIC -> KeystoreAuthorization.StrongBiometric
MobileAndroidAuthorization.USER_PRESENCE -> KeystoreAuthorization.UserPresence
}
private inline fun <T> call(block: () -> T): T = try {
block()
} catch (error: KeystoreException) {
throw when (error.kind) {
KeystoreFailure.MissingKey -> MobileAndroidKeystoreException.MissingKey()
KeystoreFailure.Invalidated -> MobileAndroidKeystoreException.Invalidated()
KeystoreFailure.Lockout -> MobileAndroidKeystoreException.Lockout()
KeystoreFailure.Locked -> MobileAndroidKeystoreException.Locked()
KeystoreFailure.Denied -> MobileAndroidKeystoreException.Denied()
KeystoreFailure.Cancelled -> MobileAndroidKeystoreException.Cancelled()
KeystoreFailure.Corrupted -> MobileAndroidKeystoreException.Corrupted()
KeystoreFailure.UnsupportedProtection ->
MobileAndroidKeystoreException.UnsupportedProtection()
KeystoreFailure.StrongboxUnavailable ->
MobileAndroidKeystoreException.StrongboxUnavailable()
KeystoreFailure.AlreadyExists -> MobileAndroidKeystoreException.AlreadyExists()
KeystoreFailure.Unavailable -> MobileAndroidKeystoreException.Unavailable()
}
}
@@ -27,8 +27,13 @@ class NativeFixtureShellRepository internal constructor(
private val scope: CoroutineScope,
private val load: suspend (ShellState) -> ShellSnapshot
) : ShellRepository {
constructor(context: Context, scope: CoroutineScope, directories: () -> PrivateDirectories) :
this(scope, nativeLoader(context, directories))
constructor(
context: Context,
scope: CoroutineScope,
directories: () -> PrivateDirectories,
keystore: AndroidKeystore
) :
this(scope, nativeLoader(context, directories, keystore))
private val mutableSnapshot = MutableStateFlow(ShellSnapshot(ShellState.Loading, emptyList()))
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
private val revision = AtomicLong()
@@ -64,7 +69,8 @@ class NativeFixtureShellRepository internal constructor(
private fun nativeLoader(
context: Context,
directoryProvider: () -> PrivateDirectories
directoryProvider: () -> PrivateDirectories,
keystore: AndroidKeystore
): suspend (ShellState) -> ShellSnapshot {
val runtime = NativeRuntime(context)
var storage: MobilePrivateRuntime? = null
@@ -85,6 +91,8 @@ private fun nativeLoader(
)
storage = initialized
initialized.activate()
val secrets = initialized.androidSecretBackend(KeystoreCallback(keystore))
initialized.verifySecureStorage(secrets)
recovery = initialized.status().recovery?.toFacade()
initialized.shell()
} catch (error: MobilePrivateStorageFfiException.Failed) {
+7
View File
@@ -22,6 +22,9 @@ cargoNdk = "4.1.2"
jna = "5.19.1"
annotation = "1.11.0"
biometric = "1.1.0"
fragment = "1.9.1"
[libraries]
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose" }
compose-ui = { module = "androidx.compose.ui:ui" }
@@ -39,11 +42,15 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
junit = { module = "junit:junit", version.ref = "junit" }
android-test-core = { module = "androidx.test:core", version.ref = "androidTest" }
android-test-runner = { module = "androidx.test:runner", version.ref = "androidTest" }
android-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
fragment = { module = "androidx.fragment:fragment", version.ref = "fragment" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
+566
View File
@@ -5,6 +5,11 @@
<verify-signatures>false</verify-signatures>
</configuration>
<components>
<component group="androidx.activity" name="activity" version="1.1.0">
<artifact name="activity-1.1.0.pom">
<sha256 value="3690001fc5a57ad176cacf7a13f56219bffb73b93c7978dbac1e1e5db082d6c0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity" version="1.12.0">
<artifact name="activity-1.12.0.aar">
<sha256 value="1af72f689de924143a03fe5b6f698a7170d7aea842f71307746e8d1db8bbfeeb" origin="Generated by Gradle"/>
@@ -26,6 +31,14 @@
<sha256 value="c4317fb95ce2716b88f1f4a5334795efda084097a3f2447ffccb10a412c85be4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity" version="1.8.1">
<artifact name="activity-1.8.1.aar">
<sha256 value="66bc2c13bd91fb8ea09e445b21f784d3e5d3a9ec9b180cd07e0e9208c2f939c6" origin="Generated by Gradle"/>
</artifact>
<artifact name="activity-1.8.1.module">
<sha256 value="bc21549bdb908da2eeccb4ac81aef9996f8faf7b52b9c840c36a348b0579403a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-compose" version="1.12.0">
<artifact name="activity-compose-1.12.0.aar">
<sha256 value="056a0700aa7e7cf5f7be1eebb32a70534b63a662c1b7408c1f2b099d75a0d13f" origin="Generated by Gradle"/>
@@ -63,11 +76,32 @@
<sha256 value="f4001a709b6f7132b22a5e17c24f8b8a9b3c462a598de3b0f16cc57a4344bab6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.activity" name="activity-ktx" version="1.8.1">
<artifact name="activity-ktx-1.8.1.aar">
<sha256 value="9f77caa2bbec7ceefa5c931e2f223c2b386a594ee0442d168dfeaf1c850aeb9d" origin="Generated by Gradle"/>
</artifact>
<artifact name="activity-ktx-1.8.1.module">
<sha256 value="bea2f811b8d8c1b8a835e11d3c68219097c597c458429d3ceafd728e0ca2c6d1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.1.0">
<artifact name="annotation-1.1.0.pom">
<sha256 value="2e9372ba7780ef44952adbf86b66e1f08682c1e5277c926185f6564a13799efe" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.11.0">
<artifact name="annotation-1.11.0.module">
<sha256 value="ac8410bea7adaabc9e71285f0627c9cb929c5addd00cfc6d0ebd421dc2da1ee0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.2.0">
<artifact name="annotation-1.2.0.jar">
<sha256 value="9029262bddce116e6d02be499e4afdba21f24c239087b76b3b57d7e98b490a36" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotation-1.2.0.module">
<sha256 value="2efcab81ef91b211bacd206eaacd995a51f633a2e96b57a8fc00144c5f9c56b3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.7.0">
<artifact name="annotation-1.7.0.module">
<sha256 value="530708656d380605077ea8bca9ae1372fbd1af38e375f411d4e43263bd510c3c" origin="Generated by Gradle"/>
@@ -78,6 +112,11 @@
<sha256 value="ac94dfef4f82ccd2ca7573a53779dbf3dfab650e654056a4d4df90af43572990" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.8.1">
<artifact name="annotation-1.8.1.module">
<sha256 value="e6386e85afdd865044e216575e493ed39a63a6325bd925379a21429c3941c8b5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.9.1">
<artifact name="annotation-1.9.1.module">
<sha256 value="f204b05b728a97561718bc716242e47c629c0085a80ee74fca53d4d638bcbe3f" origin="Generated by Gradle"/>
@@ -88,6 +127,22 @@
<sha256 value="9b6974a7dfe26d3c209dd63e16f8ee2461b57a091789160ca1eb492bb1bf3f84" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-experimental" version="1.4.0">
<artifact name="annotation-experimental-1.4.0.aar">
<sha256 value="c6eb7e676011ec65b32428373d450debdfc45179c4f8b3a752174fb87c17b08a" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotation-experimental-1.4.0.module">
<sha256 value="5930ea7f21fcb6d0deb2ba32748a0ef7c8fd2c42384860582ba7cd20deb90379" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-experimental" version="1.4.1">
<artifact name="annotation-experimental-1.4.1.aar">
<sha256 value="6bd4c7c7476f8260cd3bdbb81183583e93fc9f790c27dea7dc314181cbf87aa0" origin="Generated by Gradle"/>
</artifact>
<artifact name="annotation-experimental-1.4.1.module">
<sha256 value="2ac2f7106e12f263425b4a4dfc80989447fb895675fe902d86759aa74fd12b7d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-experimental" version="1.5.1">
<artifact name="annotation-experimental-1.5.1.aar">
<sha256 value="323a119b32e91d44e932fb97ed28eb568ca2a9d3ba57ec09392074b5c5e24ad4" origin="Generated by Gradle"/>
@@ -112,6 +167,11 @@
<sha256 value="07ce60c377ab94e47c8c902589b9776030064fd1a7e4d5a01a38d700e35e5db4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-jvm" version="1.8.1">
<artifact name="annotation-jvm-1.8.1.module">
<sha256 value="c959e3b0cdc75c15efe01605fa56aa79f033e39238e150638b8fabde6aa121a0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.annotation" name="annotation-jvm" version="1.9.1">
<artifact name="annotation-jvm-1.9.1.jar">
<sha256 value="1e343917ebf27ba96fe4dc52b1cad7fd32b738fbc6355bb6cd5b3b305d7212d0" origin="Generated by Gradle"/>
@@ -120,6 +180,27 @@
<sha256 value="03fb659177c8618e47425925c11bc91f384703ea26c265be9b25cd79292b511c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.appcompat" name="appcompat" version="1.2.0">
<artifact name="appcompat-1.2.0.aar">
<sha256 value="3d2131a55a61a777322e2126e0018011efa6339e53b44153eb651b16020cca70" origin="Generated by Gradle"/>
</artifact>
<artifact name="appcompat-1.2.0.module">
<sha256 value="2670902fc6c26047c42e0f60ee34656fa071841db370de958198413b5ab58fc3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.appcompat" name="appcompat-resources" version="1.2.0">
<artifact name="appcompat-resources-1.2.0.aar">
<sha256 value="c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5" origin="Generated by Gradle"/>
</artifact>
<artifact name="appcompat-resources-1.2.0.module">
<sha256 value="75d9865bc6b0f779043b4f6523a944bd2ccb7526f5c1f5ef24624ac78dec3bd3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.arch.core" name="core-common" version="2.1.0">
<artifact name="core-common-2.1.0.pom">
<sha256 value="83bbb3960eaabc600ac366c94cb59414e441532a1d6aa9388b0b8bfface5cf01" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.arch.core" name="core-common" version="2.2.0">
<artifact name="core-common-2.2.0.jar">
<sha256 value="65308a06b1c00ee186cb9e19321383f043b993813f1522c47f4a3e3303bdba41" origin="Generated by Gradle"/>
@@ -128,6 +209,14 @@
<sha256 value="edf4200cfdc2d946232252c99e5dcb9c61bb909eb5450b2613d1d4fdc974b981" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.arch.core" name="core-runtime" version="2.1.0">
<artifact name="core-runtime-2.1.0.aar">
<sha256 value="dd77615bd3dd275afb11b62df25bae46b10b4a117cd37943af45bdcbf8755852" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-runtime-2.1.0.pom">
<sha256 value="c0c4ed0160cd2ca18390015de8b392b697c173327c6b2c0947d4e62f6958c05d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.arch.core" name="core-runtime" version="2.2.0">
<artifact name="core-runtime-2.2.0.aar">
<sha256 value="a1be5e0caa2b07623862af6ae21b3ab0718123245184d0e30dea81b53f990a47" origin="Generated by Gradle"/>
@@ -144,16 +233,50 @@
<sha256 value="3677abead5d4e06cf76cbacb954151e051779c2274494451fb424e375aa9e450" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.biometric" name="biometric" version="1.1.0">
<artifact name="biometric-1.1.0.aar">
<sha256 value="270c7b7d99942d5ec1dd88594e4648feb33d8e31d8c3c2ab2321d49d9abdfc1f" origin="Generated by Gradle"/>
</artifact>
<artifact name="biometric-1.1.0.module">
<sha256 value="4999f205b2b38e31180ad58111e32d564190c2024fbfd58527e2da8bfb67ff65" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.0.0">
<artifact name="collection-1.0.0.pom">
<sha256 value="a7913a5275ad68e555d2612ebe8c14c367b153e14ca48a1872a64899020e54ef" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.1.0">
<artifact name="collection-1.1.0.jar">
<sha256 value="632a0e5407461de774409352940e292a291037724207a787820c77daf7d33b72" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-1.1.0.pom">
<sha256 value="67e9066ca4acfdc6e3cc508293c31ba0398057ff118e4f70b1e1813c9a3456d1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.4.2">
<artifact name="collection-1.4.2.module">
<sha256 value="0326d2cf5adbe592312810ca1f71c6c0cc30f753c43f07c7402361b788a7784c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.5.0">
<artifact name="collection-1.5.0.module">
<sha256 value="bfeb7bd84f3f7dda7bd73b6709d4a1f61f5a37f843728bae08a067e3df9a0aef" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection" version="1.6.0">
<artifact name="collection-1.6.0.module">
<sha256 value="2cad9397bf1cce162f14732b3cfcefa36081b422e35a17b6c3c9c0d617ec523b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-jvm" version="1.4.2">
<artifact name="collection-jvm-1.4.2.jar">
<sha256 value="984ce9bd780055eafb8a105aca3f514686bf7b1b6272ec7c645a98ce40fc7db4" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-jvm-1.4.2.module">
<sha256 value="aad6b3536c0f0e570aa733d5141d70fa6b9b3add370f73a311ca4ca5dee25448" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-jvm" version="1.5.0">
<artifact name="collection-jvm-1.5.0.jar">
<sha256 value="70b35924e4babcdffa37d0e575ee039c56a2d97123342624c48b603233704341" origin="Generated by Gradle"/>
@@ -162,6 +285,27 @@
<sha256 value="dde85e292509231b5471bb091b575099d4f43161eb281e87385038a0161072e6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-jvm" version="1.6.0">
<artifact name="collection-jvm-1.6.0.jar">
<sha256 value="839fcec147704611a222159fa84e3e062f68eee80caa549ddd1222d5210a13df" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-jvm-1.6.0.module">
<sha256 value="28d88aee55c1b5c02640cfcda134a793e2cee40ea815561dd8f6174de883dc59" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.1.0">
<artifact name="collection-ktx-1.1.0.jar">
<sha256 value="2bfc54475c047131913361f56d0f7f019c6e5bee53eeb0eb7d94a7c499a05227" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-ktx-1.1.0.pom">
<sha256 value="721e76e74ee4158d3fe9759074b7eceed4ff7d84ed34a3faca5843fb874ac946" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.4.2">
<artifact name="collection-ktx-1.4.2.module">
<sha256 value="8a68e297cd92c80789acc033e2b3d7cbd7941f9234335f00c037205df200bcf5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.5.0">
<artifact name="collection-ktx-1.5.0.jar">
<sha256 value="939b4a8697640e77d83e4e8b4c4a9d18f8130198c5961194af8dd9db9dc7c353" origin="Generated by Gradle"/>
@@ -170,6 +314,14 @@
<sha256 value="4228894e25eec7e17329b206ba72e8aad255bc535018dc62acc5eff3b5c7aaa5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.collection" name="collection-ktx" version="1.6.0">
<artifact name="collection-ktx-1.6.0.jar">
<sha256 value="939b4a8697640e77d83e4e8b4c4a9d18f8130198c5961194af8dd9db9dc7c353" origin="Generated by Gradle"/>
</artifact>
<artifact name="collection-ktx-1.6.0.module">
<sha256 value="9d79ed80238d17125ead24a57fd7f9032ac4cda71c99710f60d7eb62a34fffae" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.compose" name="compose-bom" version="2026.09.00">
<artifact name="compose-bom-2026.09.00.pom">
<sha256 value="74791c31313009659bd0bf09008bd514a163aa04452dd4cb82df2fb971c757c3" origin="Generated by Gradle"/>
@@ -464,6 +616,14 @@
<sha256 value="384d529ad668ada4808de29b29a6348f0007c2841ad79cae9a5a089bd68810f5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.13.1">
<artifact name="core-1.13.1.aar">
<sha256 value="2c27de199535675005553066597a4b20fa1eea7c228ab4ef6b32b5fe39ca1f59" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-1.13.1.module">
<sha256 value="2a10979bbb3bcd7b25b7f664ab4e9b016fabf2174a26768b677e12e4bea4c7c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.16.0">
<artifact name="core-1.16.0.aar">
<sha256 value="6bf03d39dbe3744acce227d3b697374c3625aae1025fbec8ad9fd7bd58bce431" origin="Generated by Gradle"/>
@@ -480,11 +640,24 @@
<sha256 value="b67ff7a1f9be95687dfa86d6055147fc1d8bbe3b1dc19048c432afca18abfd95" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.3.2">
<artifact name="core-1.3.2.pom">
<sha256 value="afb5ea494dd083ed404cd51f580d218e37362f8ae326e893bee521290ed34920" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.8.0">
<artifact name="core-1.8.0.module">
<sha256 value="505f1838869611519d65ec7feb650e88856e3682e37e42a2a24b73e655a98d74" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core" version="1.9.0">
<artifact name="core-1.9.0.aar">
<sha256 value="8bda3ee3a88887d54f6679fb6b6cd788629f73234ac91c8bbed924e721ec85b8" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-1.9.0.module">
<sha256 value="8674ade1d12962469dd2d1e6edc03b465f20d180b0840a129bc40b351e8651d1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.1.0">
<artifact name="core-ktx-1.1.0.pom">
<sha256 value="729d11bad16d87c6212dc9ab80e9c770d333115062136e0732164a6b1696e11c" origin="Generated by Gradle"/>
@@ -495,6 +668,14 @@
<sha256 value="a3e6a0692e05fd59e6ac380715ed72b325c625a3627646c69d108bdcddc4260f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.13.1">
<artifact name="core-ktx-1.13.1.aar">
<sha256 value="19ba50d094c7368ede1b4ccf1195ceb83e35970736593f823e5af716f8d05d70" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-ktx-1.13.1.module">
<sha256 value="ab530b04e2fbe73205484899a3be56c90968873eb496fc52e5ea7596d91035d1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.16.0">
<artifact name="core-ktx-1.16.0.aar">
<sha256 value="1766dbd82f64a12dcd359ecb6f15f3cf35db4d66d22961a7c51b2ffc6468314c" origin="Generated by Gradle"/>
@@ -516,6 +697,19 @@
<sha256 value="29eaddba4e6a35e68a128e594ebae3b72ef7e767f1b273ef93dc2ed019ff97f6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.8.0">
<artifact name="core-ktx-1.8.0.module">
<sha256 value="a91bc3e02f209f643dd8275345a9e3003ce20d64fc0760eccf479c1709842f72" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-ktx" version="1.9.0">
<artifact name="core-ktx-1.9.0.aar">
<sha256 value="1542a1df7be908c95fee91b8dbc03fd974f53f11d84a85d881f95a52551c4051" origin="Generated by Gradle"/>
</artifact>
<artifact name="core-ktx-1.9.0.module">
<sha256 value="4d46646066c794f2812d5b33a9422d37e4f918c4d809cd1fb2579c7022ef2818" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.core" name="core-viewtree" version="1.0.0">
<artifact name="core-viewtree-1.0.0.aar">
<sha256 value="dc1b678d58ebcf2bfa1587be68ff826694ce3d221251b9ef30d4d4b36297e6de" origin="Generated by Gradle"/>
@@ -524,6 +718,14 @@
<sha256 value="11386cfa46cbbfddb6a4059f14354c00691cf65d3d63c3618818a83326ef3c7f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.cursoradapter" name="cursoradapter" version="1.0.0">
<artifact name="cursoradapter-1.0.0.aar">
<sha256 value="a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564" origin="Generated by Gradle"/>
</artifact>
<artifact name="cursoradapter-1.0.0.pom">
<sha256 value="62d95c89850af21030b19f14d5f7ecd6d8bcc9a3014c59002ec99624caac8100" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.customview" name="customview" version="1.0.0">
<artifact name="customview-1.0.0.aar">
<sha256 value="20e5b8f6526a34595a604f56718da81167c0b40a7a94a57daa355663f2594df2" origin="Generated by Gradle"/>
@@ -556,6 +758,14 @@
<sha256 value="37d01baf6f0869f099ebdc6fb39f3dd3d267f17f24753d2587ccc43bc45685a1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.drawerlayout" name="drawerlayout" version="1.0.0">
<artifact name="drawerlayout-1.0.0.aar">
<sha256 value="9402442cdc5a43cf62fb14f8cf98c63342d4d9d9b805c8033c6cf7e802749ac1" origin="Generated by Gradle"/>
</artifact>
<artifact name="drawerlayout-1.0.0.pom">
<sha256 value="da6733425a83f5ce850878fa7201082345fed7c668dd58550fdd19c0396c6fa4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.emoji2" name="emoji2" version="1.2.0">
<artifact name="emoji2-1.2.0.module">
<sha256 value="9d1996cca03777baa1f27cd15531db983a633dae37b90f85bd53501acb56699d" origin="Generated by Gradle"/>
@@ -574,6 +784,11 @@
<sha256 value="92be16cccb757f807bddd92f1fca95a038a810ccca051c4149c11682478954b8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment" version="1.2.5">
<artifact name="fragment-1.2.5.pom">
<sha256 value="df0aca46b62bb47cc662cbcee63372db6d2a2859478ee38b594fba3433fe30a5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment" version="1.5.1">
<artifact name="fragment-1.5.1.aar">
<sha256 value="82b04780eda21b0a5eea02475415887a1cadfb368b371221aeb40703c067f338" origin="Generated by Gradle"/>
@@ -582,6 +797,30 @@
<sha256 value="2a665e166553b99bb789cef517e64131059df2a9ee60c1fd76b53cba7f0b4131" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment" version="1.8.9">
<artifact name="fragment-1.8.9.aar">
<sha256 value="12de5b89ab6641c1d8da82b63dc816652622b06d2781b949b6fbd2dff9fac492" origin="Generated by Gradle"/>
</artifact>
<artifact name="fragment-1.8.9.module">
<sha256 value="67af03fb14d0e2bae2a7c62185d3a5607a915ed1fc494952afb165cd12b00b7c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment" version="1.9.1">
<artifact name="fragment-1.9.1.aar">
<sha256 value="72f70815c0a4d502f15c57e5e6bc861e64c5338ea06de75aed83969b17a2a727" origin="Generated by Gradle"/>
</artifact>
<artifact name="fragment-1.9.1.module">
<sha256 value="066557e66adc00d5d53f67890f15620170ecb8700a0f214393748156b70fa66a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment-ktx" version="1.8.9">
<artifact name="fragment-ktx-1.8.9.aar">
<sha256 value="078ad5877b04f4bf7350198579d81f9c8796fa20e46b4ade3f231a370456c5ad" origin="Generated by Gradle"/>
</artifact>
<artifact name="fragment-ktx-1.8.9.module">
<sha256 value="4e66c1b0f47b94d3e0dc41607d3af6b292996e98f065debedcbf8c791400081f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.graphics" name="graphics-path" version="1.0.1">
<artifact name="graphics-path-1.0.1.aar">
<sha256 value="8ca4032b6d79b351f0b59ad4b580eddbb9423e1652f7c958830687f1eee2ec03" origin="Generated by Gradle"/>
@@ -598,16 +837,39 @@
<sha256 value="0ddc07cc39699f48ecd9ec894b5830c0f09e22e82959294edf37217224c88b7b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.10.0">
<artifact name="lifecycle-common-2.10.0.module">
<sha256 value="5d1247b1edfbdc9ddedcbe525c9d2629567b1c538cb7a9c620cd044a124b5c73" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.11.0">
<artifact name="lifecycle-common-2.11.0.module">
<sha256 value="5b3db432c61a8f1668023d0aeb3e2cbc3ef337e5da22db9afb10b7513d1d4e5a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.2.0">
<artifact name="lifecycle-common-2.2.0.pom">
<sha256 value="cc30a8a2a97d3767bd169b747c078727308096dd3ab7917d80b2f1f42d118bd6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.3.1">
<artifact name="lifecycle-common-2.3.1.module">
<sha256 value="5fb7c8514d8c56cada5e29ef89dc0289e71942ab4cb0b2e6dca137b9dcb8fdd4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.5.1">
<artifact name="lifecycle-common-2.5.1.module">
<sha256 value="7d4bc2961cd5bd399e3621d434f0c453dd6cadf891f917a946cc291abdda8f1a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.6.1">
<artifact name="lifecycle-common-2.6.1.jar">
<sha256 value="f34831b6c71cd844e1d35d1be49d5e79447c5ab856346531b1e8676fda7374b1" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-common-2.6.1.module">
<sha256 value="93747a9145cb36bc71005f598ede32e2b1149ade5a16e62b0e4969345bc62d85" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.9.4">
<artifact name="lifecycle-common-2.9.4.module">
<sha256 value="c578bd755ea46d7a77811309a3539538e2d7fb86a05fc2ad81c655ecf7dff7a4" origin="Generated by Gradle"/>
@@ -621,6 +883,14 @@
<sha256 value="1c8c3fc840e4c565568baa57ba886aee5ed869df316335b056d92cdbbc84af6f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common-jvm" version="2.10.0">
<artifact name="lifecycle-common-jvm-2.10.0.jar">
<sha256 value="15943082d87bcd78830398fa377f2c8b12643de290d0906ed8e49a2d335ddb54" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-common-jvm-2.10.0.module">
<sha256 value="93d9016b3afd03639a407f51a119d5ca4dae988de376c380f0e51dd9d88e686d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common-jvm" version="2.11.0">
<artifact name="lifecycle-common-jvm-2.11.0.jar">
<sha256 value="e6a66e67b9f63a0fb6344f165c1d872376e5fe6d235bffb138af45122cf452a9" origin="Generated by Gradle"/>
@@ -629,6 +899,14 @@
<sha256 value="c9477d6784c8dcb992f1a1774dd95df31f3e101e5d1e9cb7b01aaff3924a1942" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.10.0">
<artifact name="lifecycle-livedata-2.10.0.aar">
<sha256 value="3e8c009fc88da1c52e4ed05a81269d5ac9bd405046f300307f5e3a17e201a6df" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-2.10.0.module">
<sha256 value="3a6d473b3104c983501de4883d6ba7684dc83330c460ef958390a8265b078310" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.11.0">
<artifact name="lifecycle-livedata-2.11.0.aar">
<sha256 value="1bc28e40e06ff5e0478b9276ca6287ce6993415b70910745ac916b14dec9b72f" origin="Generated by Gradle"/>
@@ -637,6 +915,22 @@
<sha256 value="d990bc920ae43729d702d80077328e8a59781ea5a35b24c02bc47d326ae73d7d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.6.1">
<artifact name="lifecycle-livedata-2.6.1.aar">
<sha256 value="67359f609dfc2bf65da1270b23033f856064ec279f058e0a70c715f7c9003031" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-2.6.1.module">
<sha256 value="e2659e87e890b52b66f300471828b387a205e5387a392713190166f399ecf641" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.10.0">
<artifact name="lifecycle-livedata-core-2.10.0.aar">
<sha256 value="12dd61a9843ccebb4547d3ebe2f6d030ca8f6988d22f8fad19c0af93b4a97e95" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-core-2.10.0.module">
<sha256 value="1d83bd5f3ccc1298eda25b9ed128e8c187f830e7f3af8d0294be688abb03c35d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.11.0">
<artifact name="lifecycle-livedata-core-2.11.0.aar">
<sha256 value="39b26ef1e081fc88d7054ff1a63d0a5d71876373c33a711ab789c8228d025ad1" origin="Generated by Gradle"/>
@@ -645,6 +939,27 @@
<sha256 value="d1c1410c8ce9cf1463616c19ca7d8afca63bcbbac9dabd29da3121d61bd41343" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.2.0">
<artifact name="lifecycle-livedata-core-2.2.0.pom">
<sha256 value="aa51621be3817d1f5d21336b0c0525fe96ae899cec656c13d8ea18a51ea32c2f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core" version="2.6.1">
<artifact name="lifecycle-livedata-core-2.6.1.aar">
<sha256 value="2256780a3cff4a1e57fbb3d442557c17dc363ab8af105bcaf5261d8e2d5db949" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-core-2.6.1.module">
<sha256 value="e9c0dc3f0ac54419c0cfed8ff5cecb82943a7c58f7a54169f0d849b2c60a3552" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core-ktx" version="2.10.0">
<artifact name="lifecycle-livedata-core-ktx-2.10.0.aar">
<sha256 value="5f18429749a183e19178d8665ae42bf7b6310b50358ae9e0e0cc452afd6e0bc3" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-core-ktx-2.10.0.module">
<sha256 value="2b28a03201f3077b2ae3e2853f983944afdf518b02939acfc9fe9e5fcab87275" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core-ktx" version="2.11.0">
<artifact name="lifecycle-livedata-core-ktx-2.11.0.aar">
<sha256 value="408fac99b61a7ca9d0620f4f24891f85c14ee007cfd027abc3df759a31b0086a" origin="Generated by Gradle"/>
@@ -653,6 +968,14 @@
<sha256 value="b66cb413ac9cde811c9b96e4d2b3d4ff2ef403236187b3db33fe4f1da8e95fcd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata-core-ktx" version="2.6.1">
<artifact name="lifecycle-livedata-core-ktx-2.6.1.aar">
<sha256 value="9f2c16e33a55ba8d67a3623a55befff2ec80643e3f0a926760df1d957c3f8aef" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-livedata-core-ktx-2.6.1.module">
<sha256 value="d8699c515516aee3a0613de951649d9f1e137d33fedf077be0a6aa9e01ae211f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-process" version="2.11.0">
<artifact name="lifecycle-process-2.11.0.aar">
<sha256 value="9743f0c7baf27d34497b7e9a1cd497d8260251b03124507963abfaad66c3f552" origin="Generated by Gradle"/>
@@ -661,16 +984,47 @@
<sha256 value="7d9d7ad473370a6147d9fc9b1d1c6af14b55a474849483e5974940bdf8b52fdd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.0.0">
<artifact name="lifecycle-runtime-2.0.0.pom">
<sha256 value="a92a46fa7aec8ac326a5d578734a2d5b63206976996b9e06cae171b35b0ab6de" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.10.0">
<artifact name="lifecycle-runtime-2.10.0.module">
<sha256 value="da805fa017a4acce13f501469a75a3f306248eebc58f5bcc28019b0015dfba65" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.11.0">
<artifact name="lifecycle-runtime-2.11.0.module">
<sha256 value="5cf1c15ce1fd9329b8ef75517c309e3aab967bd785d96ae100c3d9c8906e317e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.2.0">
<artifact name="lifecycle-runtime-2.2.0.pom">
<sha256 value="7f154066fed1c9162870f728b208352831b3d1f8f23f5d388958a50ca4e9f441" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.6.1">
<artifact name="lifecycle-runtime-2.6.1.aar">
<sha256 value="4867fd5279742fba8388821930cb2affe06d81a52814e7e41e70392ea0ef887c" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-runtime-2.6.1.module">
<sha256 value="a4cbb01a42d07047bd8d870017c96a1b0b7b4673320e86b66317a13be2ec10c7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime" version="2.9.4">
<artifact name="lifecycle-runtime-2.9.4.module">
<sha256 value="dcbc5b5b50666e84258a74bfd8e514918c593a4ffcef0c0358562a02fbd80c58" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-android" version="2.10.0">
<artifact name="lifecycle-runtime-android-2.10.0.module">
<sha256 value="749b6e7a4422914581e0795d9d48fb4f96daa3fee97747421ff4234860185f47" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-runtime.aar">
<sha256 value="2193a1573d623f37b20c7d27d1a8f903a72f651cc6f32e5794f85dda744fee75" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-android" version="2.11.0">
<artifact name="lifecycle-runtime-android-2.11.0.module">
<sha256 value="25d7a9baeb72fbdba65756865a900c37a6146e5b3f835c44fc0493b15bd19286" origin="Generated by Gradle"/>
@@ -692,11 +1046,32 @@
<sha256 value="7b31489e6e162143c481f14e1ae5a4dc089f8ec58e65976a0502b910c641987f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-ktx" version="2.10.0">
<artifact name="lifecycle-runtime-ktx-2.10.0.module">
<sha256 value="556f15957376858b6e3abc08fb3df566ea5eb4067149253194bf2a049606db4e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-ktx" version="2.11.0">
<artifact name="lifecycle-runtime-ktx-2.11.0.module">
<sha256 value="bb6ba31505096941a988cc69f7d490514629cd45cb5da29cda79a9bde4bcdce5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-ktx" version="2.6.1">
<artifact name="lifecycle-runtime-ktx-2.6.1.aar">
<sha256 value="28a0b834f40edd52e2f8010bdc3057d2a0cc76aaa5ac9311adb0b9ce919ca9cc" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-runtime-ktx-2.6.1.module">
<sha256 value="39854f32c9b010f652f4e5041ca4ce06981dcb6954128c1e8e1cc02e63ab185f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-ktx-android" version="2.10.0">
<artifact name="lifecycle-runtime-ktx-android-2.10.0.module">
<sha256 value="980a1be146cafa16ab3b1680991dde14b2077a4b13d5b31427cb98dc8930f24b" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-runtime-ktx.aar">
<sha256 value="871883703339f87284d232cb436e317c6f4af5612d92b43f27c6b13b92109fa7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-runtime-ktx-android" version="2.11.0">
<artifact name="lifecycle-runtime-ktx-android-2.11.0.module">
<sha256 value="2e756a084e928f367b22dccb89f775d5562ff634d4b9c4e5e5415a9e00f8accd" origin="Generated by Gradle"/>
@@ -705,6 +1080,14 @@
<sha256 value="1fead96607f8b4ebd2bc5840c6fcf08b09fc47ceb1665e4b381dce80b19f32a0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.10.0">
<artifact name="lifecycle-viewmodel-2.10.0.aar">
<sha256 value="4b809cf9eb7323d200504e95921feb777ff080215cadb8237742d5ed0ba5b792" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-2.10.0.module">
<sha256 value="4fd71df733245c46e4b88e85b4a90b82c9af4a359ef31f2bdf250f93a02c7452" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.11.0">
<artifact name="lifecycle-viewmodel-2.11.0.aar">
<sha256 value="4b809cf9eb7323d200504e95921feb777ff080215cadb8237742d5ed0ba5b792" origin="Generated by Gradle"/>
@@ -713,16 +1096,37 @@
<sha256 value="8393aa093e7e848902de9b3ccc0a12269106ac6e773d75ff97f9754295c49298" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.2.0">
<artifact name="lifecycle-viewmodel-2.2.0.pom">
<sha256 value="aaa3afaa49ff47e2bc9b2389f939d3e8137616c4616684e2dfb53ba8366aa11d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.5.1">
<artifact name="lifecycle-viewmodel-2.5.1.module">
<sha256 value="01e413b73cbe38cb714dc5bdb21bd860931124c8e5f2369803f4aacc49081c9f" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.6.1">
<artifact name="lifecycle-viewmodel-2.6.1.aar">
<sha256 value="e4ff4338999e1c6c9c724719f5d4aa7dd61bf6f545d5256a27a9d375df9f2330" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-2.6.1.module">
<sha256 value="2b406faea5c12f2b8df4b7a60931f846648f2e1f4d78361e198d1184f19a4797" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel" version="2.9.4">
<artifact name="lifecycle-viewmodel-2.9.4.module">
<sha256 value="964d69d6b877296e8b79a77bb826ef5fb186aecaa7a1a7ff776fb2250c993006" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-android" version="2.10.0">
<artifact name="lifecycle-viewmodel-android-2.10.0.module">
<sha256 value="06b07a07ae17c7592a30c533c3d46a125fcb1bef096d7e6134e2420db2464d0a" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel.aar">
<sha256 value="9303287030027ca0b8cf98a748ddeb121ec1bfa1312c0069428e4765bbabce78" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-android" version="2.11.0">
<artifact name="lifecycle-viewmodel-android-2.11.0.module">
<sha256 value="646813f4c50bc5f198bb96a26f1accf319da09d09c0bffbf35e8dcf2173d8393" origin="Generated by Gradle"/>
@@ -744,6 +1148,14 @@
<sha256 value="2bafd402807bd4af05c06cf58c48cbdad09521e2145273faae90908b7712f689" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-ktx" version="2.10.0">
<artifact name="lifecycle-viewmodel-ktx-2.10.0.aar">
<sha256 value="54295ac86b67886dc0613e38f621c531995d0662005fc763e340e19a90994f84" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-ktx-2.10.0.module">
<sha256 value="837bfdd848eefef77544fa289dbff4c428680dd9820641eadb841cc7a74c83f9" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-ktx" version="2.11.0">
<artifact name="lifecycle-viewmodel-ktx-2.11.0.aar">
<sha256 value="230af6c866076d320d92c22c7c44bb52afca97f6892ddf987e2906ef2919998f" origin="Generated by Gradle"/>
@@ -752,6 +1164,24 @@
<sha256 value="2b35a1381397614ad13cf301cb3685a15be4d50d1584376e7e5418010bd4096d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-ktx" version="2.6.1">
<artifact name="lifecycle-viewmodel-ktx-2.6.1.aar">
<sha256 value="7fc0fa234a3321b1f34db5862b17da83d1c62c1bc66ec2fd4f1bb0a771acfabb" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-ktx-2.6.1.module">
<sha256 value="a5110f85788bc167df9fe8fbe08004ddc5c5dd9b15cb7376ceeaef025fccaaf8" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate" version="1.0.0">
<artifact name="lifecycle-viewmodel-savedstate-1.0.0.pom">
<sha256 value="37a46395fb334f5d54843d5823f37513c80485dcae6cf16188babc49ebe65e17" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate" version="2.10.0">
<artifact name="lifecycle-viewmodel-savedstate-2.10.0.module">
<sha256 value="50857781e3ddf8e39905e4e4a57a30496c245f9901d1b7d3f17382382188347d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate" version="2.11.0">
<artifact name="lifecycle-viewmodel-savedstate-2.11.0.module">
<sha256 value="dc6750a6991bfaac0817469865fe2ccb01e26186a0386299a5d056bac2cc72cc" origin="Generated by Gradle"/>
@@ -762,6 +1192,22 @@
<sha256 value="29acd5fe614b3f89123eb838f688d625eaa8b422c8d1905b48ad8e760cd7ad8b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate" version="2.6.1">
<artifact name="lifecycle-viewmodel-savedstate-2.6.1.aar">
<sha256 value="c82f89221adbe19df7c7adbab63f4ecc857fc746e3c9494256ab8fa5c20491b2" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-savedstate-2.6.1.module">
<sha256 value="dafb8649763d29c29cda27bc22fcdab9a9efc53c0fff9ae3de90882eabaa8944" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate-android" version="2.10.0">
<artifact name="lifecycle-viewmodel-savedstate-android-2.10.0.module">
<sha256 value="949d2a862b6b584cee91af70a9e017bdb117d225bb46096da9424a1728ca79e9" origin="Generated by Gradle"/>
</artifact>
<artifact name="lifecycle-viewmodel-savedstate.aar">
<sha256 value="19404239fa584322c2f81e9659e162bc011b746d29dc950a11d260624d83225e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-viewmodel-savedstate-android" version="2.11.0">
<artifact name="lifecycle-viewmodel-savedstate-android-2.11.0.module">
<sha256 value="4351e91c13b1e6282cae924276799e0efdba67b801f12142479964e0b9b9f7ee" origin="Generated by Gradle"/>
@@ -835,6 +1281,14 @@
<sha256 value="4c978461881d0506dd8d38deb25f84f56ea28756a14e583f2561c0ee98dff2d6" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.profileinstaller" name="profileinstaller" version="1.3.1">
<artifact name="profileinstaller-1.3.1.aar">
<sha256 value="d0e402ec31f24028a1dc7eb6a0a3f9d9635c1459392cd734396343b73d673948" origin="Generated by Gradle"/>
</artifact>
<artifact name="profileinstaller-1.3.1.module">
<sha256 value="cc7eed0ed4b669de84b852f78797a50018a4f30002e1e38aaa668af22ca5b460" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.profileinstaller" name="profileinstaller" version="1.4.0">
<artifact name="profileinstaller-1.4.0.aar">
<sha256 value="d502141fcce902430f62b674c32aecd0f75262e7ee2cd15c74adb617cd13130a" origin="Generated by Gradle"/>
@@ -843,12 +1297,20 @@
<sha256 value="39bf997a28d8eed2cb319819f6f352a1ba3a78b9c979004fbe05e26b8f7d160b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate" version="1.0.0">
<artifact name="savedstate-1.0.0.pom">
<sha256 value="844d7d22fcea798c78bf5559229daf88ec9cad8136f5eea0a29aa76b3f8fde7c" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate" version="1.2.0">
<artifact name="savedstate-1.2.0.module">
<sha256 value="4247c23308abcb5d0ff8cdaf7dfd583f3c2a1016f68d13f1a41c21600b6fafd7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate" version="1.2.1">
<artifact name="savedstate-1.2.1.aar">
<sha256 value="21a7d4bcf6bdb94ad7b9283801529300b4fbb8808ca4f191e0cdce6fd8e4705a" origin="Generated by Gradle"/>
</artifact>
<artifact name="savedstate-1.2.1.module">
<sha256 value="5bb656fc760d9e3996b535160cbb4106033c9f736e9089e6ef4eb0c669785066" origin="Generated by Gradle"/>
</artifact>
@@ -879,6 +1341,14 @@
<sha256 value="0f0ae3032d6c6eea834ee22179c8976160d09d841d30e47f53f56b1b3438774b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate-ktx" version="1.2.1">
<artifact name="savedstate-ktx-1.2.1.aar">
<sha256 value="8553f87e7136c24ec5243560f48f1c32cba56daa77722f89589a5cafcb8f7894" origin="Generated by Gradle"/>
</artifact>
<artifact name="savedstate-ktx-1.2.1.module">
<sha256 value="94359184b2ba51c0f498a2b9055d37b372231ef9bcc54a4972ac99f0303afff1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.savedstate" name="savedstate-ktx" version="1.3.1">
<artifact name="savedstate-ktx-1.3.1.module">
<sha256 value="59007d8d33cf166fdfd1a9bf56ccfd212e9822c04b3a741966face5918610655" origin="Generated by Gradle"/>
@@ -994,6 +1464,9 @@
</artifact>
</component>
<component group="androidx.tracing" name="tracing" version="1.0.0">
<artifact name="tracing-1.0.0.aar">
<sha256 value="07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612" origin="Generated by Gradle"/>
</artifact>
<artifact name="tracing-1.0.0.module">
<sha256 value="fc8b21ebe5fa3a7c96ee098bcdcd00f077ebce73f243fa858e2b0671615f75d8" origin="Generated by Gradle"/>
</artifact>
@@ -1014,11 +1487,45 @@
<sha256 value="d0d8d486b6bd33206dbf3f1a6d167e9b43c268ea63c3321c886b1543ad05ece3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.tracing" name="tracing" version="2.0.0">
<artifact name="tracing-2.0.0.module">
<sha256 value="407139f993e891940a7f71a3cca099f5ee8d7828845bbb8239717eedf80c9553" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.tracing" name="tracing-android" version="2.0.0">
<artifact name="tracing-android-2.0.0.module">
<sha256 value="d2baa5464529169efa7ae5e7351b424c9b56d445f090d1824d1f87d1472e4510" origin="Generated by Gradle"/>
</artifact>
<artifact name="tracing.aar">
<sha256 value="b21800b83ed0e3e214742c8a94edee90130e0a9970c85e59161d161546f5fe9a" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.vectordrawable" name="vectordrawable" version="1.1.0">
<artifact name="vectordrawable-1.1.0.aar">
<sha256 value="46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26" origin="Generated by Gradle"/>
</artifact>
<artifact name="vectordrawable-1.1.0.pom">
<sha256 value="5b0e2d5b2179e54804785cbc21ce5f473b5e1ddd55a57da482e94dcd39492bb2" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.vectordrawable" name="vectordrawable-animated" version="1.1.0">
<artifact name="vectordrawable-animated-1.1.0.aar">
<sha256 value="76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8" origin="Generated by Gradle"/>
</artifact>
<artifact name="vectordrawable-animated-1.1.0.pom">
<sha256 value="276a20116b705fb75b9003ee9496c56f6fd3b32375fb232472811eba60a040bd" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.versionedparcelable" name="versionedparcelable" version="1.0.0">
<artifact name="versionedparcelable-1.0.0.pom">
<sha256 value="47ee7c5543239a651f8fb81310cadeeaab8e38a2d844d7bb92dd086e4ffbb320" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.versionedparcelable" name="versionedparcelable" version="1.1.0">
<artifact name="versionedparcelable-1.1.0.pom">
<sha256 value="c729c7be0cc06323bda829d460666e79dbd43b799a21089a44bd3b293dc253b5" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="androidx.versionedparcelable" name="versionedparcelable" version="1.1.1">
<artifact name="versionedparcelable-1.1.1.aar">
<sha256 value="57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4" origin="Generated by Gradle"/>
@@ -3020,6 +3527,11 @@
<sha256 value="425d1683a903d085a1718122918242ad6ad0246c79283786697395e4d3a8c801" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="1.9.21">
<artifact name="kotlin-stdlib-1.9.21.module">
<sha256 value="d3019f7f0d71924ce47298c9cc46af0245f75219719b35c5915fbcc7e7a69395" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib" version="2.2.0">
<artifact name="kotlin-stdlib-2.2.0.jar">
<sha256 value="65d12d85a3b865c160db9147851712a64b10dadd68b22eea22a95bf8a8670dca" origin="Generated by Gradle"/>
@@ -3097,6 +3609,11 @@
<sha256 value="58fc0d664fc3a67e7e6be9fdbeaedbd212dfa3e527f744f861e792cda72c0e47" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-common" version="2.4.21">
<artifact name="kotlin-stdlib-common-2.4.21.module">
<sha256 value="aeae4f24688e1dd7ebee648ab2b12be685cbe33e9c72bc1439730a794d6e2d5b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin" name="kotlin-stdlib-jdk7" version="1.8.0">
<artifact name="kotlin-stdlib-jdk7-1.8.0.jar">
<sha256 value="4c889d1d9803f5f2eb6c1592a6b7e62369ac7660c9eee15aba16fec059163666" origin="Generated by Gradle"/>
@@ -3221,6 +3738,22 @@
<sha256 value="7b7d1dddf188817deaad738e92e11faa5abdf937c87120cdf036102566ad4be3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.6.4">
<artifact name="kotlinx-coroutines-android-1.6.4.jar">
<sha256 value="3fdc0eed5bc4b83ee9622774520a2db25470370eacd1581cac1e37704f095b00" origin="Generated by Gradle"/>
</artifact>
<artifact name="kotlinx-coroutines-android-1.6.4.module">
<sha256 value="68516559e6f84a621b9783cd892a64630ccd7875843588ddb3f0501425e33f15" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-android" version="1.9.0">
<artifact name="kotlinx-coroutines-android-1.9.0.jar">
<sha256 value="bd783acd2f9738845d58380f46f45b5cde95d0cb03027d56725338b26bfc4d72" origin="Generated by Gradle"/>
</artifact>
<artifact name="kotlinx-coroutines-android-1.9.0.module">
<sha256 value="7df1e04d77f0cc411890d999a9449b0e3bcd4f159a46c3060b110204ca607d43" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.10.2">
<artifact name="kotlinx-coroutines-bom-1.10.2.pom">
<sha256 value="faf0c6538e53ddc0499a63664d8e763c216580b2e18e722ccbdf1b431a6afe26" origin="Generated by Gradle"/>
@@ -3231,6 +3764,16 @@
<sha256 value="f65860bce58a2bfadf05fdc516d38333421f0e387fef87e24449c0394c80d254" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.6.4">
<artifact name="kotlinx-coroutines-bom-1.6.4.pom">
<sha256 value="ab2614855fba66aa8a42514dbe3d5a884315ffe1ed63f5932e710a8006245ce1" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.7.3">
<artifact name="kotlinx-coroutines-bom-1.7.3.pom">
<sha256 value="4e5d1900e6379ef3f5970d04a8f30529adc82f859e8cc107c21ce8149ef666c4" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.8.1">
<artifact name="kotlinx-coroutines-bom-1.8.1.pom">
<sha256 value="563e4aa29fa8fe09a6e1746d0a5b51308f7c7dedc468dc5a0ca810a485877030" origin="Generated by Gradle"/>
@@ -3246,6 +3789,21 @@
<sha256 value="1e19785d34728b7525d7a77ff32ead4547d51cc7a45d4ac44d4de608169b0c0d" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.6.4">
<artifact name="kotlinx-coroutines-core-1.6.4.module">
<sha256 value="a6eed4a1835588e7c84fcd7b0475fce9a7b3444c870ebc797b88ba64ccf4576b" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.7.3">
<artifact name="kotlinx-coroutines-core-1.7.3.module">
<sha256 value="7fb162396594ec28e1b6a4411b457949a7670f5e12019176774e1fd6b9471bbf" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.8.1">
<artifact name="kotlinx-coroutines-core-1.8.1.module">
<sha256 value="08cbaf3325b54e0f8ef8da85e4eb596f7d946f843e5d1600385463f3268a4ef0" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core" version="1.9.0">
<artifact name="kotlinx-coroutines-core-1.9.0.module">
<sha256 value="ad534034a953b4e12cbeeb874c66adf8b1ca14df15fe0d2e6547aa34e86dfeca" origin="Generated by Gradle"/>
@@ -3267,6 +3825,14 @@
<sha256 value="5b255653ad6b1f21e0c46108605f129e3aa56ed9414849cc9f5b436441517528" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.6.4">
<artifact name="kotlinx-coroutines-core-jvm-1.6.4.jar">
<sha256 value="c24c8bb27bb320c4a93871501a7e5e0c61607638907b197aef675513d4c820be" origin="Generated by Gradle"/>
</artifact>
<artifact name="kotlinx-coroutines-core-jvm-1.6.4.module">
<sha256 value="0d94c8a41483e7c2707ebd693e1b1357a84152998ce85550ebbc54ca4321a3a7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-core-jvm" version="1.8.0">
<artifact name="kotlinx-coroutines-core-jvm-1.8.0-sources.jar">
<sha256 value="451599ff2044e6a98f975b28d106891cc664ae69ad7a30c572dffb5413202179" origin="Generated by Gradle"/>
+6
View File
@@ -12,11 +12,13 @@ android {
.toInt()
ndkVersion = libs.versions.ndk.get()
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
minSdk =
libs.versions.minSdk
.get()
.toInt()
}
testOptions { targetSdk = libs.versions.compileSdk.get().toInt() }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@@ -35,5 +37,9 @@ kotlin {
dependencies {
implementation(project(":core"))
implementation(libs.biometric)
implementation(libs.fragment)
testImplementation(libs.junit)
androidTestImplementation(libs.android.test.runner)
androidTestImplementation(libs.android.test.core)
}
+71 -7
View File
@@ -2,8 +2,66 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
# To regenerate this file, run: ./gradlew :platform:dependencies --write-locks
androidx.activity:activity-ktx:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.activity:activity:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-jvm:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-jvm:1.9.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.annotation:annotation:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation:1.9.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.appcompat:appcompat-resources:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.appcompat:appcompat:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.biometric:biometric:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-jvm:1.4.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.collection:collection-jvm:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection:1.4.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.collection:collection:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.concurrent:concurrent-futures-ktx:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.concurrent:concurrent-futures:1.1.0=debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.concurrent:concurrent-futures:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.core:core-ktx:1.13.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core-ktx:1.9.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.core:core-viewtree:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core:1.13.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core:1.9.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.drawerlayout:drawerlayout:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.fragment:fragment:1.9.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.interpolator:interpolator:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-common-jvm:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-common:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-livedata-core-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-livedata-core:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-livedata:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-runtime-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-runtime-ktx-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-runtime-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-runtime:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel:2.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.profileinstaller:profileinstaller:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.startup:startup-runtime:1.1.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.test.services:storage:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:core:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:monitor:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:runner:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.tracing:tracing-android:2.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.tracing:tracing:1.1.0=debugAndroidTestCompileClasspath
androidx.tracing:tracing:2.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
ch.qos.logback:logback-classic:1.3.15=ktlint
ch.qos.logback:logback-core:1.3.15=ktlint
com.android.tools.analytics-library:protos:32.4.1=androidLintTool
@@ -42,6 +100,7 @@ com.google.code.gson:gson:2.11.0=androidLintTool
com.google.errorprone:error_prone_annotations:2.36.0=androidLintTool
com.google.guava:failureaccess:1.0.2=androidLintTool
com.google.guava:guava:33.4.0-jre=androidLintTool
com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidLintTool
com.google.j2objc:j2objc-annotations:3.0.0=androidLintTool
com.google.jimfs:jimfs:1.1=androidLintTool
@@ -75,7 +134,7 @@ io.github.oshai:kotlin-logging:5.1.0=ktlint,ktlintBaselineReporter,ktlintReporte
jakarta.activation:jakarta.activation-api:1.2.1=androidLintTool
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=androidLintTool
javax.inject:javax.inject:1=androidLintTool
junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
net.java.dev.jna:jna-platform:5.6.0=androidLintTool
net.java.dev.jna:jna:5.19.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
net.java.dev.jna:jna:5.6.0=androidLintTool
@@ -94,7 +153,7 @@ org.checkerframework:checker-qual:3.43.0=androidLintTool
org.ec4j.core:ec4j-core:1.1.1=ktlint,ktlintBaselineReporter,ktlintRuleset
org.glassfish.jaxb:jaxb-runtime:2.3.2=androidLintTool
org.glassfish.jaxb:txw2:2.3.2=androidLintTool
org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-build-tools-api:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-build-tools-api:2.4.21=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
org.jetbrains.kotlin:kotlin-build-tools-compat:2.4.21=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
@@ -128,19 +187,24 @@ org.jetbrains.kotlin:kotlin-stdlib:2.4.0=kotlinAbiValidationCompatClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.4.21=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-tooling-core:2.4.21=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool,debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintBaselineReporter,ktlintRuleset
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool,debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool,debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.4.1=ktlintReporter
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-core:1.4.1=ktlintReporter
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.4.1=ktlintReporter
org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1=ktlintReporter
org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintBaselineReporter,ktlintReporter,ktlintRuleset,releaseCompileClasspath
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-gradle-work-action
org.jetbrains:annotations:13.0=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,ktlint,ktlintBaselineReporter,ktlintReporter,ktlintRuleset
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-gradle-work-action
org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.junit.platform:junit-platform-commons:1.13.3=unified-test-platform-gradle-work-action
org.junit.platform:junit-platform-engine:1.13.3=unified-test-platform-gradle-work-action
org.junit.platform:junit-platform-launcher:1.13.3=unified-test-platform-gradle-work-action
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="de.rfc1437.ironstorage.platform.KeystoreTestActivity" android:exported="false" android:theme="@android:style/Theme.Material.Light.NoActionBar" />
</application>
</manifest>
@@ -0,0 +1,359 @@
package de.rfc1437.ironstorage.platform
import android.security.keystore.KeyInfo
import androidx.test.core.app.ActivityScenario
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import de.rfc1437.ironstorage.core.KeystoreAuthorization
import de.rfc1437.ironstorage.core.KeystoreException
import de.rfc1437.ironstorage.core.KeystoreFailure
import de.rfc1437.ironstorage.core.KeystoreKeySpecification
import java.security.KeyStore
import java.util.UUID
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class AndroidKeystoreTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
@Before fun removeAbandonedFixtureKeys() {
val os = AndroidKeystoreAdapter(context)
os.aliases().filter {
it.startsWith("ironstorage.issue93.fixture.")
}.forEach { os.deleteKey(it) }
}
private fun alias(): String = "ironstorage.issue93.fixture.${UUID.randomUUID()}"
private fun specification(
alias: String,
auth: KeystoreAuthorization = KeystoreAuthorization.None
) = KeystoreKeySpecification(
alias,
auth,
true,
auth == KeystoreAuthorization.StrongBiometric,
false
)
private fun failure(expected: KeystoreFailure, block: () -> Unit) {
try {
block()
throw AssertionError("Expected typed failure")
} catch (
error: KeystoreException
) {
assertEquals(expected, error.kind)
assertEquals("Keystore operation failed", error.message)
}
}
@Test fun deviceUnlockedRoundtripReplaceDeleteAndTamper() {
val os = AndroidKeystoreAdapter(context)
assertFalse(os.deviceState().locked)
val alias = alias()
try {
os.createKey(specification(alias))
failure(KeystoreFailure.AlreadyExists) { os.createKey(specification(alias)) }
val clear = "public-test-token".toByteArray()
val encrypted = os.encrypt(alias, KeystoreAuthorization.None, byteArrayOf(1, 2), clear)
assertTrue(clear.all { it == 0.toByte() })
assertEquals(12, encrypted.nonce.size)
val retrieved = os.decrypt(
alias,
KeystoreAuthorization.None,
byteArrayOf(1, 2),
encrypted
)
try {
assertArrayEquals("public-test-token".toByteArray(), retrieved)
} finally {
retrieved.fill(0)
}
failure(KeystoreFailure.Corrupted) {
os.decrypt(alias, KeystoreAuthorization.None, byteArrayOf(3), encrypted)
}
encrypted.bytes[encrypted.bytes.lastIndex] =
(encrypted.bytes.last().toInt() xor 1).toByte()
failure(KeystoreFailure.Corrupted) {
os.decrypt(alias, KeystoreAuthorization.None, byteArrayOf(1, 2), encrypted)
}
val store = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val key = store.getKey(alias, null) as SecretKey
val info = SecretKeyFactory.getInstance(
key.algorithm,
"AndroidKeyStore"
).getKeySpec(key, KeyInfo::class.java) as KeyInfo
assertEquals(256, info.keySize)
assertFalse(info.isUserAuthenticationRequired)
assertTrue(os.aliases().contains(alias))
os.deleteKey(alias)
failure(KeystoreFailure.MissingKey) {
os.decrypt(alias, KeystoreAuthorization.None, byteArrayOf(1, 2), encrypted)
}
os.deleteKey(alias)
} finally {
os.deleteKey(alias)
}
}
@Test fun biometricRoundtripAndCancellation() {
val os = AndroidKeystoreAdapter(context)
assertTrue(
"Enroll synthetic fingerprint before this test",
os.deviceState().strongBiometric
)
val alias = alias()
ActivityScenario.launch(KeystoreTestActivity::class.java).use { scenario ->
lateinit var host: KeystoreTestActivity
scenario.onActivity {
host = it
os.attach(it)
}
try {
os.createKey(specification(alias, KeystoreAuthorization.StrongBiometric))
val store = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val key = store.getKey(alias, null) as SecretKey
val info = SecretKeyFactory.getInstance(
key.algorithm,
"AndroidKeyStore"
).getKeySpec(key, KeyInfo::class.java) as KeyInfo
assertTrue(info.isUserAuthenticationRequired)
assertTrue(info.isInvalidatedByBiometricEnrollment)
assertEquals(0, info.userAuthenticationValidityDurationSeconds)
host.stage("93:biometric-encrypt")
val encrypted = os.encrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(4),
"public-passphrase".toByteArray()
)
host.stage("93:biometric-decrypt")
val clear = os.decrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(4),
encrypted
)
try {
assertArrayEquals("public-passphrase".toByteArray(), clear)
} finally {
clear.fill(0)
}
host.stage("93:biometric-cancel")
failure(KeystoreFailure.Cancelled) {
os.decrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(4),
encrypted
)
}
host.stage("93:complete")
} catch (error: KeystoreException) {
throw AssertionError("Typed Keystore failure: ${error.kind}", error)
} finally {
os.cancelPending()
os.detach(host)
os.deleteKey(alias)
}
}
}
@Test fun userPresenceSupportsTheConfiguredCredentialFlow() {
val os = AndroidKeystoreAdapter(context)
val alias = alias()
ActivityScenario.launch(KeystoreTestActivity::class.java).use { scenario ->
lateinit var host: KeystoreTestActivity
scenario.onActivity {
host = it
os.attach(it)
}
try {
os.createKey(specification(alias, KeystoreAuthorization.UserPresence))
host.stage("93:presence-encrypt")
val encrypted = os.encrypt(
alias,
KeystoreAuthorization.UserPresence,
byteArrayOf(7),
"public-passphrase".toByteArray()
)
host.stage("93:presence-decrypt")
val clear = os.decrypt(
alias,
KeystoreAuthorization.UserPresence,
byteArrayOf(7),
encrypted
)
try {
assertArrayEquals("public-passphrase".toByteArray(), clear)
} finally {
clear.fill(0)
}
} catch (error: KeystoreException) {
throw AssertionError("Typed Keystore failure: ${error.kind}", error)
} finally {
os.cancelPending()
os.detach(host)
os.deleteKey(alias)
}
}
}
@Test
@SdkSuppress(maxSdkVersion = 36)
fun biometricLockoutIsTyped() {
val os = AndroidKeystoreAdapter(context)
val alias = alias()
ActivityScenario.launch(KeystoreTestActivity::class.java).use { scenario ->
lateinit var host: KeystoreTestActivity
scenario.onActivity {
host = it
os.attach(it)
}
try {
os.createKey(specification(alias, KeystoreAuthorization.StrongBiometric))
host.stage("93:biometric-lockout")
failure(KeystoreFailure.Lockout) {
os.encrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(8),
"public-passphrase".toByteArray()
)
}
} finally {
os.cancelPending()
os.detach(host)
os.deleteKey(alias)
}
}
}
@Test
@SdkSuppress(minSdkVersion = 37)
fun biometricLockoutRecoveryStillRequiresMatchingBiometric() {
val os = AndroidKeystoreAdapter(context)
val alias = alias()
ActivityScenario.launch(KeystoreTestActivity::class.java).use { scenario ->
lateinit var host: KeystoreTestActivity
scenario.onActivity {
host = it
os.attach(it)
}
try {
os.createKey(specification(alias, KeystoreAuthorization.StrongBiometric))
host.stage("93:biometric-lockout-recovery")
failure(KeystoreFailure.Cancelled) {
os.encrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(8),
"public-passphrase".toByteArray()
)
}
} finally {
os.cancelPending()
os.detach(host)
os.deleteKey(alias)
}
}
}
@Test fun addingBiometricInvalidatesTheProtectedKey() = invalidation("93:add-enrollment")
@Test fun removingAllBiometricsInvalidatesTheProtectedKey() = invalidation(
"93:remove-enrollment"
)
@Test fun removingSecureLockInvalidatesTheProtectedKey() =
invalidation("93:remove-lock", KeystoreFailure.MissingKey)
private fun invalidation(
stage: String,
expected: KeystoreFailure = KeystoreFailure.Invalidated
) {
val os = AndroidKeystoreAdapter(context)
assertTrue(os.deviceState().strongBiometric)
val alias = alias()
val credentialAlias = alias()
ActivityScenario.launch(KeystoreTestActivity::class.java).use { scenario ->
lateinit var host: KeystoreTestActivity
scenario.onActivity {
host = it
os.attach(it)
}
try {
os.createKey(specification(credentialAlias))
val credential = os.encrypt(
credentialAlias,
KeystoreAuthorization.None,
byteArrayOf(10),
"public-token".toByteArray()
)
os.createKey(specification(alias, KeystoreAuthorization.StrongBiometric))
host.stage("93:biometric-encrypt")
val encrypted = os.encrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(9),
"public-passphrase".toByteArray()
)
assertTrue(
"Complete the indicated emulator fixture change",
host.awaitStep(stage)
)
if (expected ==
KeystoreFailure.MissingKey
) {
assertFalse(os.aliases().contains(alias))
}
val token = os.decrypt(
credentialAlias,
KeystoreAuthorization.None,
byteArrayOf(10),
credential
)
try {
assertArrayEquals("public-token".toByteArray(), token)
} finally {
token.fill(0)
}
failure(expected) {
os.decrypt(
alias,
KeystoreAuthorization.StrongBiometric,
byteArrayOf(9),
encrypted
)
}
} catch (error: KeystoreException) {
throw AssertionError("Typed Keystore failure: ${error.kind}", error)
} finally {
os.cancelPending()
os.detach(host)
os.deleteKey(alias)
os.deleteKey(credentialAlias)
}
}
}
@Test fun protectedKeyRequiresAnAuthenticationHost() {
val os = AndroidKeystoreAdapter(context)
assertTrue(os.deviceState().strongBiometric)
val alias = alias()
try {
os.createKey(specification(alias, KeystoreAuthorization.StrongBiometric))
val value = "public-test-passphrase".toByteArray()
failure(KeystoreFailure.Unavailable) {
os.encrypt(alias, KeystoreAuthorization.StrongBiometric, byteArrayOf(1), value)
}
assertTrue(value.all { it == 0.toByte() })
} finally {
os.deleteKey(alias)
}
}
}
@@ -0,0 +1,50 @@
package de.rfc1437.ironstorage.platform
import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.FragmentActivity
import de.rfc1437.ironstorage.platform.test.R
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
/** Instrumentation host, omitted from release builds; shows stages, never secrets. */
class KeystoreTestActivity : FragmentActivity() {
private lateinit var label: TextView
private val step = AtomicReference<CountDownLatch?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
label = TextView(this).apply {
setText(R.string.keystore_fixture_title)
textSize = 24f
}
val next = Button(this).apply {
setText(R.string.keystore_fixture_continue)
setOnClickListener { step.get()?.countDown() }
}
setContentView(
LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, 80, 0, 0)
addView(label)
addView(next)
}
)
}
fun awaitStep(value: String): Boolean {
val next = CountDownLatch(1)
step.set(next)
stage(value)
return try {
next.await(180, TimeUnit.SECONDS)
} finally {
step.set(null)
}
}
fun stage(value: String) {
android.util.Log.i("IronStorage93", value)
runOnUiThread { label.text = value }
}
}
@@ -0,0 +1,4 @@
<resources>
<string name="keystore_fixture_title" translatable="false">Keystore fixture</string>
<string name="keystore_fixture_continue" translatable="false">Continue fixture</string>
</resources>
@@ -1 +1,3 @@
<manifest />
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
</manifest>
@@ -0,0 +1,306 @@
package de.rfc1437.ironstorage.platform
import android.app.KeyguardManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.KeyProperties
import android.security.keystore.StrongBoxUnavailableException
import android.security.keystore.UserNotAuthenticatedException
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import de.rfc1437.ironstorage.core.AndroidKeystore
import de.rfc1437.ironstorage.core.KeystoreAuthorization
import de.rfc1437.ironstorage.core.KeystoreCiphertext
import de.rfc1437.ironstorage.core.KeystoreDeviceState
import de.rfc1437.ironstorage.core.KeystoreException
import de.rfc1437.ironstorage.core.KeystoreFailure
import de.rfc1437.ironstorage.core.KeystoreKeySpecification
import java.lang.ref.WeakReference
import java.security.KeyStore
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import javax.crypto.AEADBadTagException
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
/** Android API mechanics only; ciphertext is returned to Rust for private persistence. */
class AndroidKeystoreAdapter(context: Context) : AndroidKeystore {
private val context = context.applicationContext
private val keyguard = context.getSystemService(KeyguardManager::class.java)
private val handler = Handler(Looper.getMainLooper())
private var activity = WeakReference<FragmentActivity>(null)
private val pending = AtomicReference<Pending?>()
fun attach(host: FragmentActivity) {
activity = WeakReference(host)
}
fun detach(host: FragmentActivity) {
if (activity.get() === host) {
cancelPending()
activity.clear()
}
}
override fun deviceState(): KeystoreDeviceState = KeystoreDeviceState(
Build.VERSION.SDK_INT,
keyguard.isDeviceLocked,
keyguard.isDeviceSecure,
BiometricManager.from(
context
).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) ==
BiometricManager.BIOMETRIC_SUCCESS,
Build.VERSION.SDK_INT >= 28 &&
context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
)
override fun createKey(specification: KeystoreKeySpecification) = operation {
val store = store()
if (store.containsAlias(specification.alias)) fail(KeystoreFailure.AlreadyExists)
val builder = KeyGenParameterSpec.Builder(
specification.alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
if (specification.unlockedDevice) {
if (Build.VERSION.SDK_INT < 28) fail(KeystoreFailure.UnsupportedProtection)
builder.setUnlockedDeviceRequired(true)
}
if (specification.strongbox) {
if (Build.VERSION.SDK_INT < 28) fail(KeystoreFailure.StrongboxUnavailable)
builder.setIsStrongBoxBacked(true)
}
if (specification.authorization != KeystoreAuthorization.None) {
builder.setUserAuthenticationRequired(true)
if (Build.VERSION.SDK_INT >= 30) {
val authenticators = when (specification.authorization) {
KeystoreAuthorization.StrongBiometric -> KeyProperties.AUTH_BIOMETRIC_STRONG
KeystoreAuthorization.UserPresence ->
KeyProperties.AUTH_BIOMETRIC_STRONG or
KeyProperties.AUTH_DEVICE_CREDENTIAL
KeystoreAuthorization.None -> fail(KeystoreFailure.UnsupportedProtection)
}
builder.setUserAuthenticationParameters(0, authenticators)
} else {
if (specification.authorization !=
KeystoreAuthorization.StrongBiometric
) {
fail(KeystoreFailure.UnsupportedProtection)
}
@Suppress("DEPRECATION")
builder.setUserAuthenticationValidityDurationSeconds(-1)
}
builder.setInvalidatedByBiometricEnrollment(specification.invalidateOnEnrollment)
}
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore").apply {
init(builder.build())
generateKey()
}
Unit
}
override fun encrypt(
alias: String,
authorization: KeystoreAuthorization,
aad: ByteArray,
value: ByteArray
): KeystoreCiphertext = operation {
try {
requireUnlocked()
val cipher = cipher(alias, Cipher.ENCRYPT_MODE, null)
// Auth-bound providers can process AAD immediately.
authenticate(cipher, authorization)
cipher.updateAAD(aad)
val bytes = cipher.doFinal(value)
requireUnlocked()
KeystoreCiphertext(cipher.iv, bytes)
} finally {
value.fill(0)
}
}
override fun decrypt(
alias: String,
authorization: KeystoreAuthorization,
aad: ByteArray,
value: KeystoreCiphertext
): ByteArray = operation {
requireUnlocked()
if (value.nonce.size != 12 || value.bytes.size < 17) fail(KeystoreFailure.Corrupted)
val cipher = cipher(alias, Cipher.DECRYPT_MODE, value.nonce)
authenticate(cipher, authorization)
cipher.updateAAD(aad)
val clear = cipher.doFinal(value.bytes)
try {
requireUnlocked()
clear
} catch (error: Exception) {
clear.fill(0)
throw error
}
}
override fun deleteKey(alias: String) = operation { store().deleteEntry(alias) }
override fun aliases(): List<String> = operation { store().aliases().toList() }
override fun cancelPending() {
pending.getAndSet(null)?.let { request ->
request.error.set(KeystoreFailure.Cancelled)
request.done.countDown()
handler.post {
request.prompt?.cancelAuthentication()
request.prompt = null
}
}
}
private fun store(): KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
private fun cipher(alias: String, mode: Int, nonce: ByteArray?): Cipher {
val key = store().getKey(alias, null) as? SecretKey
?: fail(KeystoreFailure.MissingKey)
return Cipher.getInstance("AES/GCM/NoPadding").apply {
if (nonce == null) {
init(mode, key)
} else {
init(mode, key, GCMParameterSpec(128, nonce))
}
}
}
private fun requireUnlocked() {
if (keyguard.isDeviceLocked) fail(KeystoreFailure.Locked)
}
private class Pending {
val done = CountDownLatch(1)
val error = AtomicReference<KeystoreFailure?>()
val authenticated = AtomicReference<Cipher?>()
var prompt: BiometricPrompt? = null
}
private fun authenticate(cipher: Cipher, authorization: KeystoreAuthorization) {
if (authorization == KeystoreAuthorization.None) return
if (Looper.myLooper() == Looper.getMainLooper()) fail(KeystoreFailure.Unavailable)
val request = Pending()
if (!pending.compareAndSet(null, request)) fail(KeystoreFailure.Unavailable)
handler.post {
if (pending.get() !== request) return@post
val host = activity.get()
if (host == null || host.isFinishing || host.isDestroyed) {
request.error.set(KeystoreFailure.Unavailable)
request.done.countDown()
return@post
}
try {
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult
) {
if (pending.get() ===
request
) {
request.authenticated.set(result.cryptoObject?.cipher)
}
request.done.countDown()
}
override fun onAuthenticationError(code: Int, message: CharSequence) {
request.error.set(
when (code) {
BiometricPrompt.ERROR_CANCELED,
BiometricPrompt.ERROR_USER_CANCELED,
BiometricPrompt.ERROR_NEGATIVE_BUTTON ->
KeystoreFailure.Cancelled
BiometricPrompt.ERROR_LOCKOUT,
BiometricPrompt.ERROR_LOCKOUT_PERMANENT ->
KeystoreFailure.Lockout
BiometricPrompt.ERROR_NO_BIOMETRICS,
BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL,
BiometricPrompt.ERROR_HW_NOT_PRESENT ->
KeystoreFailure.UnsupportedProtection
else -> KeystoreFailure.Denied
}
)
request.done.countDown()
}
}
val prompt = BiometricPrompt(host, { handler.post(it) }, callback)
request.prompt = prompt
val information = BiometricPrompt.PromptInfo.Builder().setTitle(
context.getString(R.string.keystore_prompt_title)
)
if (authorization == KeystoreAuthorization.StrongBiometric) {
information.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG
).setNegativeButtonText(context.getString(R.string.keystore_prompt_cancel))
} else {
if (Build.VERSION.SDK_INT < 30) fail(KeystoreFailure.UnsupportedProtection)
information.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
}
prompt.authenticate(information.build(), BiometricPrompt.CryptoObject(cipher))
} catch (error: Exception) {
request.error.set(
if (error is KeystoreException) error.kind else KeystoreFailure.Unavailable
)
request.done.countDown()
}
}
try {
if (!request.done.await(180, TimeUnit.SECONDS)) fail(KeystoreFailure.Cancelled)
request.error.get()?.let { fail(it) }
if (request.authenticated.get() !== cipher ||
pending.get() !== request
) {
fail(KeystoreFailure.Cancelled)
}
} catch (_: InterruptedException) {
Thread.currentThread().interrupt()
fail(KeystoreFailure.Cancelled)
} finally {
pending.compareAndSet(request, null)
val succeeded = request.authenticated.getAndSet(null) === cipher
handler.post {
if (!succeeded) request.prompt?.cancelAuthentication()
request.prompt = null
}
}
}
}
private fun fail(kind: KeystoreFailure): Nothing = throw KeystoreException(kind)
private inline fun <T> operation(block: () -> T): T = try {
block()
} catch (error: Exception) {
if (error is KeystoreException) throw error
var cause: Throwable? = error
var kind = KeystoreFailure.Unavailable
repeat(8) {
val osError = cause
if (kind == KeystoreFailure.Unavailable && Build.VERSION.SDK_INT >= 33 &&
osError is android.security.KeyStoreException
) {
kind = when (osError.numericErrorCode) {
android.security.KeyStoreException.ERROR_USER_AUTHENTICATION_REQUIRED ->
KeystoreFailure.Locked
android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST ->
KeystoreFailure.MissingKey
android.security.KeyStoreException.ERROR_KEY_CORRUPTED -> KeystoreFailure.Corrupted
android.security.KeyStoreException.ERROR_PERMISSION_DENIED -> KeystoreFailure.Denied
android.security.KeyStoreException.ERROR_UNIMPLEMENTED ->
KeystoreFailure.UnsupportedProtection
else -> kind
}
}
when {
cause is KeyPermanentlyInvalidatedException -> kind = KeystoreFailure.Invalidated
cause is UserNotAuthenticatedException -> kind = KeystoreFailure.Locked
cause is AEADBadTagException -> kind = KeystoreFailure.Corrupted
Build.VERSION.SDK_INT >= 28 && cause is StrongBoxUnavailableException ->
kind = KeystoreFailure.StrongboxUnavailable
}
cause = cause?.cause
}
throw KeystoreException(kind)
}
@@ -0,0 +1,4 @@
<resources>
<string name="keystore_prompt_title">Unlock protected secret</string>
<string name="keystore_prompt_cancel">Cancel</string>
</resources>
@@ -0,0 +1,119 @@
package de.rfc1437.ironstorage.bridge
import androidx.test.platform.app.InstrumentationRegistry
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidAuthorization
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidCiphertext
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidDeviceState
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeySpecification
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystore
import de.rfc1437.ironstorage.bridge.generated.MobileAndroidKeystoreException
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageErrorKind
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageFfiException
import de.rfc1437.ironstorage.bridge.generated.MobileSecretException
import de.rfc1437.ironstorage.bridge.generated.MobileSecretLocator
import de.rfc1437.ironstorage.bridge.generated.MobileSecretProtection
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
class KeystoreContractTest {
@Test fun rustOwnsRecordsPoliciesAndForeignSecretLifetime() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
NativeRuntime(context).privateStorage(
MobilePrivateDirectories(
context.filesDir.canonicalPath,
context.noBackupFilesDir.canonicalPath,
context.cacheDir.canonicalPath
)
).use { runtime ->
val os = FixtureKeystore()
val backend = runtime.androidSecretBackend(os)
val locator = MobileSecretLocator.HttpsGitCredential("issue93-server", "fixture")
val protection = MobileSecretProtection.DEVICE_UNLOCKED
backend.unlock()
try {
backend.create(locator, protection, "public-token".toByteArray())
assertThrows(MobileSecretException.AlreadyExists::class.java) {
backend.create(locator, protection, "duplicate".toByteArray())
}
backend.replace(locator, protection, "replacement".toByteArray())
val clear = backend.retrieve(locator, protection)
try {
assertArrayEquals("replacement".toByteArray(), clear)
assertTrue(os.returned.all { it == 0.toByte() })
} finally {
clear.fill(0)
}
os.lockout = true
assertThrows(MobileSecretException.Lockout::class.java) {
backend.retrieve(locator, protection)
}
os.lockout = false
backend.lock()
assertThrows(MobileSecretException.Locked::class.java) {
backend.retrieve(locator, protection)
}
backend.unlock()
assertThrows(MobileSecretException.UnsupportedProtection::class.java) {
backend.create(
MobileSecretLocator.OpenPgpPassphrase("A".repeat(40)),
protection,
"public-passphrase".toByteArray()
)
}
} finally {
backend.delete(locator, protection)
}
assertTrue(os.keys.isEmpty())
os.corruptAliases = true
val error = assertThrows(MobilePrivateStorageFfiException.Failed::class.java) {
runtime.androidSecretBackend(os)
}
assertEquals(MobilePrivateStorageErrorKind.INVALID_SECURE_BINDING, error.kind)
}
}
// Public fixture cipher only: production encryption is tested in the platform test APK.
private class FixtureKeystore : MobileAndroidKeystore {
val keys = mutableSetOf<String>()
var returned = byteArrayOf()
var lockout = false
var corruptAliases = false
override fun deviceState() = MobileAndroidDeviceState(37u, false, true, true, false)
override fun createKey(specification: MobileAndroidKeySpecification) {
assertEquals(MobileAndroidAuthorization.NONE, specification.authorization)
keys.add(specification.alias)
}
override fun encrypt(
alias: String,
authorization: MobileAndroidAuthorization,
aad: ByteArray,
value: ByteArray
): MobileAndroidCiphertext = try {
MobileAndroidCiphertext(ByteArray(12), value.copyOf(value.size + 16))
} finally {
value.fill(0)
}
override fun decrypt(
alias: String,
authorization: MobileAndroidAuthorization,
aad: ByteArray,
value: MobileAndroidCiphertext
): ByteArray {
if (lockout) throw MobileAndroidKeystoreException.Lockout()
returned = value.bytes.copyOf(value.bytes.size - 16)
return returned
}
override fun deleteKey(alias: String) {
keys.remove(alias)
}
override fun aliases(): List<String> {
if (corruptAliases) throw MobileAndroidKeystoreException.Corrupted()
return keys.toList()
}
override fun cancelPending() = Unit
}
}
+888
View File
@@ -608,6 +608,433 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer {
public protocol MobileAndroidKeystore: AnyObject, Sendable {
func deviceState() throws -> MobileAndroidDeviceState
func createKey(specification: MobileAndroidKeySpecification) throws
func encrypt(alias: String, authorization: MobileAndroidAuthorization, aad: Data, value: Data) throws -> MobileAndroidCiphertext
func decrypt(alias: String, authorization: MobileAndroidAuthorization, aad: Data, value: MobileAndroidCiphertext) throws -> Data
func deleteKey(alias: String) throws
func aliases() throws -> [String]
func cancelPending()
}
open class MobileAndroidKeystoreImpl: MobileAndroidKeystore, @unchecked Sendable {
fileprivate let handle: UInt64
/// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct NoHandle {
public init() {}
}
// TODO: We'd like this to be `private` but for Swifty reasons,
// we can't implement `FfiConverter` without making this `required` and we can't
// make it `required` without making it `public`.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
required public init(unsafeFromHandle handle: UInt64) {
self.handle = handle
}
// This constructor can be used to instantiate a fake object.
// - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
//
// - Warning:
// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash.
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public init(noHandle: NoHandle) {
self.handle = 0
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func uniffiCloneHandle() -> UInt64 {
return try! rustCall { uniffi_ironstorage_mobile_fn_clone_mobileandroidkeystore(self.handle, $0) }
}
// No primary constructor declared for this class.
deinit {
if handle == 0 {
// Mock objects have handle=0 don't try to free them
return
}
try! rustCall { uniffi_ironstorage_mobile_fn_free_mobileandroidkeystore(handle, $0) }
}
open func deviceState()throws -> MobileAndroidDeviceState {
return try FfiConverterTypeMobileAndroidDeviceState_lift(try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_device_state(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
open func createKey(specification: MobileAndroidKeySpecification)throws {try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_create_key(
self.uniffiCloneHandle(),
FfiConverterTypeMobileAndroidKeySpecification_lower(specification),uniffiCallStatus
)
}
}
open func encrypt(alias: String, authorization: MobileAndroidAuthorization, aad: Data, value: Data)throws -> MobileAndroidCiphertext {
return try FfiConverterTypeMobileAndroidCiphertext_lift(try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_encrypt(
self.uniffiCloneHandle(),
FfiConverterString.lower(alias),
FfiConverterTypeMobileAndroidAuthorization_lower(authorization),
FfiConverterData.lower(aad),
FfiConverterData.lower(value),uniffiCallStatus
)
})
}
open func decrypt(alias: String, authorization: MobileAndroidAuthorization, aad: Data, value: MobileAndroidCiphertext)throws -> Data {
return try FfiConverterData.lift(try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_decrypt(
self.uniffiCloneHandle(),
FfiConverterString.lower(alias),
FfiConverterTypeMobileAndroidAuthorization_lower(authorization),
FfiConverterData.lower(aad),
FfiConverterTypeMobileAndroidCiphertext_lower(value),uniffiCallStatus
)
})
}
open func deleteKey(alias: String)throws {try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_delete_key(
self.uniffiCloneHandle(),
FfiConverterString.lower(alias),uniffiCallStatus
)
}
}
open func aliases()throws -> [String] {
return try FfiConverterSequenceString.lift(try rustCallWithError(FfiConverterTypeMobileAndroidKeystoreError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_aliases(
self.uniffiCloneHandle(),uniffiCallStatus
)
})
}
open func cancelPending() {try! rustCall() {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_cancel_pending(
self.uniffiCloneHandle(),uniffiCallStatus
)
}
}
}
// Put the implementation in a struct so we don't pollute the top-level namespace
fileprivate struct UniffiCallbackInterfaceMobileAndroidKeystore {
// Create the VTable using a series of closures.
// Swift automatically converts these into C callback functions.
//
// Store the vtable directly.
static let vtable: UniffiVTableCallbackInterfaceMobileAndroidKeystore = UniffiVTableCallbackInterfaceMobileAndroidKeystore(
uniffiFree: { (uniffiHandle: UInt64) -> () in
do {
try FfiConverterTypeMobileAndroidKeystore.handleMap.remove(handle: uniffiHandle)
} catch {
print("Uniffi callback interface MobileAndroidKeystore: handle missing in uniffiFree")
}
},
uniffiClone: { (uniffiHandle: UInt64) -> UInt64 in
do {
return try FfiConverterTypeMobileAndroidKeystore.handleMap.clone(handle: uniffiHandle)
} catch {
fatalError("Uniffi callback interface MobileAndroidKeystore: handle missing in uniffiClone")
}
},
deviceState: { (
uniffiHandle: UInt64,
uniffiOutReturn: UnsafeMutablePointer<RustBuffer>,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> MobileAndroidDeviceState in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.deviceState(
)
}
let writeReturn = { uniffiOutReturn.pointee = FfiConverterTypeMobileAndroidDeviceState_lower($0) }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
createKey: { (
uniffiHandle: UInt64,
specification: RustBuffer,
uniffiOutReturn: UnsafeMutableRawPointer,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> () in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.createKey(
specification: try FfiConverterTypeMobileAndroidKeySpecification_lift(specification)
)
}
let writeReturn = { () }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
encrypt: { (
uniffiHandle: UInt64,
alias: RustBuffer,
authorization: RustBuffer,
aad: RustBuffer,
value: RustBuffer,
uniffiOutReturn: UnsafeMutablePointer<RustBuffer>,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> MobileAndroidCiphertext in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.encrypt(
alias: try FfiConverterString.lift(alias),
authorization: try FfiConverterTypeMobileAndroidAuthorization_lift(authorization),
aad: try FfiConverterData.lift(aad),
value: try FfiConverterData.lift(value)
)
}
let writeReturn = { uniffiOutReturn.pointee = FfiConverterTypeMobileAndroidCiphertext_lower($0) }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
decrypt: { (
uniffiHandle: UInt64,
alias: RustBuffer,
authorization: RustBuffer,
aad: RustBuffer,
value: RustBuffer,
uniffiOutReturn: UnsafeMutablePointer<RustBuffer>,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> Data in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.decrypt(
alias: try FfiConverterString.lift(alias),
authorization: try FfiConverterTypeMobileAndroidAuthorization_lift(authorization),
aad: try FfiConverterData.lift(aad),
value: try FfiConverterTypeMobileAndroidCiphertext_lift(value)
)
}
let writeReturn = { uniffiOutReturn.pointee = FfiConverterData.lower($0) }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
deleteKey: { (
uniffiHandle: UInt64,
alias: RustBuffer,
uniffiOutReturn: UnsafeMutableRawPointer,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> () in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.deleteKey(
alias: try FfiConverterString.lift(alias)
)
}
let writeReturn = { () }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
aliases: { (
uniffiHandle: UInt64,
uniffiOutReturn: UnsafeMutablePointer<RustBuffer>,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> [String] in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return try uniffiObj.aliases(
)
}
let writeReturn = { uniffiOutReturn.pointee = FfiConverterSequenceString.lower($0) }
uniffiTraitInterfaceCallWithError(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn,
lowerError: FfiConverterTypeMobileAndroidKeystoreError_lower
)
},
cancelPending: { (
uniffiHandle: UInt64,
uniffiOutReturn: UnsafeMutableRawPointer,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> () in
guard let uniffiObj = try? FfiConverterTypeMobileAndroidKeystore.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return uniffiObj.cancelPending(
)
}
let writeReturn = { () }
uniffiTraitInterfaceCall(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn
)
}
)
// Rust stores this pointer for future callback invocations, so it must live
// for the process lifetime (not just for the init function call).
//
// `nonisolated(unsafe)` is needed under Swift 6 strict concurrency.
// This is safe because the pointee is initialized once during static init
// and never mutated by either side of the FFI. Its fields are C function pointers.
nonisolated(unsafe) static let vtablePtr: UnsafePointer<UniffiVTableCallbackInterfaceMobileAndroidKeystore> = {
let ptr = UnsafeMutablePointer<UniffiVTableCallbackInterfaceMobileAndroidKeystore>.allocate(capacity: 1)
ptr.initialize(to: vtable)
return UnsafePointer(ptr)
}()
}
private func uniffiCallbackInitMobileAndroidKeystore() {
uniffi_ironstorage_mobile_fn_init_callback_vtable_mobileandroidkeystore(UniffiCallbackInterfaceMobileAndroidKeystore.vtablePtr)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidKeystore: FfiConverter {
fileprivate static let handleMap = UniffiHandleMap<MobileAndroidKeystore>()
typealias FfiType = UInt64
typealias SwiftType = MobileAndroidKeystore
public static func lift(_ handle: UInt64) throws -> MobileAndroidKeystore {
if ((handle & 1) == 0) {
// Rust-generated handle, construct a new class that uses the handle to implement the
// interface
return MobileAndroidKeystoreImpl(unsafeFromHandle: handle)
} else {
// Swift-generated handle, get the object from the handle map
return try handleMap.remove(handle: handle)
}
}
public static func lower(_ value: MobileAndroidKeystore) -> UInt64 {
if let rustImpl = value as? MobileAndroidKeystoreImpl {
// Rust-implemented object. Clone the handle and return it
return rustImpl.uniffiCloneHandle()
} else {
// Swift object, generate a new vtable handle and return that.
return handleMap.insert(obj: value)
}
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidKeystore {
let handle: UInt64 = try readInt(&buf)
return try lift(handle)
}
public static func write(_ value: MobileAndroidKeystore, into buf: inout [UInt8]) {
writeInt(&buf, lower(value))
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeystore_lift(_ handle: UInt64) throws -> MobileAndroidKeystore {
return try FfiConverterTypeMobileAndroidKeystore.lift(handle)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeystore_lower(_ value: MobileAndroidKeystore) -> UInt64 {
return FfiConverterTypeMobileAndroidKeystore.lower(value)
}
public protocol MobileAuthenticationProtocol: AnyObject, Sendable {
func acknowledgeWatchSnapshot(receipt: Data) throws -> MobileWatchSnapshotStatus
@@ -2528,6 +2955,8 @@ public protocol MobilePrivateRuntimeProtocol: AnyObject, Sendable {
func activate() throws
func androidSecretBackend(keystore: MobileAndroidKeystore) throws -> MobileSecretBackend
func authentication(backend: MobileSecretBackend, clock: MobileClock) throws -> MobileAuthentication
func clearDisposableCache() throws
@@ -2602,6 +3031,16 @@ open func activate()throws {try rustCallWithError(FfiConverterTypeMobilePrivat
}
}
open func androidSecretBackend(keystore: MobileAndroidKeystore)throws -> MobileSecretBackend {
return try FfiConverterTypeMobileSecretBackend_lift(try rustCallWithError(FfiConverterTypeMobilePrivateStorageFfiError_lift) {
uniffiCallStatus in
uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_android_secret_backend(
self.uniffiCloneHandle(),
FfiConverterTypeMobileAndroidKeystore_lower(keystore),uniffiCallStatus
)
})
}
open func authentication(backend: MobileSecretBackend, clock: MobileClock)throws -> MobileAuthentication {
return try FfiConverterTypeMobileAuthentication_lift(try rustCallWithError(FfiConverterTypeMobileAuthenticationFfiError_lift) {
uniffiCallStatus in
@@ -3230,6 +3669,192 @@ public func FfiConverterTypeMobileTotpOperation_lower(_ value: MobileTotpOperati
public struct MobileAndroidCiphertext: Equatable, Hashable {
public var nonce: Data
public var bytes: Data
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(nonce: Data, bytes: Data) {
self.nonce = nonce
self.bytes = bytes
}
}
#if compiler(>=6)
extension MobileAndroidCiphertext: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidCiphertext: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidCiphertext {
return
try MobileAndroidCiphertext(
nonce: FfiConverterData.read(from: &buf),
bytes: FfiConverterData.read(from: &buf)
)
}
public static func write(_ value: MobileAndroidCiphertext, into buf: inout [UInt8]) {
FfiConverterData.write(value.nonce, into: &buf)
FfiConverterData.write(value.bytes, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidCiphertext_lift(_ buf: RustBuffer) throws -> MobileAndroidCiphertext {
return try FfiConverterTypeMobileAndroidCiphertext.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidCiphertext_lower(_ value: MobileAndroidCiphertext) -> RustBuffer {
return FfiConverterTypeMobileAndroidCiphertext.lower(value)
}
public struct MobileAndroidDeviceState: Equatable, Hashable {
public var api: UInt32
public var locked: Bool
public var secure: Bool
public var strongBiometric: Bool
public var strongbox: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(api: UInt32, locked: Bool, secure: Bool, strongBiometric: Bool, strongbox: Bool) {
self.api = api
self.locked = locked
self.secure = secure
self.strongBiometric = strongBiometric
self.strongbox = strongbox
}
}
#if compiler(>=6)
extension MobileAndroidDeviceState: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidDeviceState: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidDeviceState {
return
try MobileAndroidDeviceState(
api: FfiConverterUInt32.read(from: &buf),
locked: FfiConverterBool.read(from: &buf),
secure: FfiConverterBool.read(from: &buf),
strongBiometric: FfiConverterBool.read(from: &buf),
strongbox: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: MobileAndroidDeviceState, into buf: inout [UInt8]) {
FfiConverterUInt32.write(value.api, into: &buf)
FfiConverterBool.write(value.locked, into: &buf)
FfiConverterBool.write(value.secure, into: &buf)
FfiConverterBool.write(value.strongBiometric, into: &buf)
FfiConverterBool.write(value.strongbox, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidDeviceState_lift(_ buf: RustBuffer) throws -> MobileAndroidDeviceState {
return try FfiConverterTypeMobileAndroidDeviceState.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidDeviceState_lower(_ value: MobileAndroidDeviceState) -> RustBuffer {
return FfiConverterTypeMobileAndroidDeviceState.lower(value)
}
public struct MobileAndroidKeySpecification: Equatable, Hashable {
public var alias: String
public var authorization: MobileAndroidAuthorization
public var unlockedDevice: Bool
public var invalidateOnEnrollment: Bool
public var strongbox: Bool
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(alias: String, authorization: MobileAndroidAuthorization, unlockedDevice: Bool, invalidateOnEnrollment: Bool, strongbox: Bool) {
self.alias = alias
self.authorization = authorization
self.unlockedDevice = unlockedDevice
self.invalidateOnEnrollment = invalidateOnEnrollment
self.strongbox = strongbox
}
}
#if compiler(>=6)
extension MobileAndroidKeySpecification: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidKeySpecification: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidKeySpecification {
return
try MobileAndroidKeySpecification(
alias: FfiConverterString.read(from: &buf),
authorization: FfiConverterTypeMobileAndroidAuthorization.read(from: &buf),
unlockedDevice: FfiConverterBool.read(from: &buf),
invalidateOnEnrollment: FfiConverterBool.read(from: &buf),
strongbox: FfiConverterBool.read(from: &buf)
)
}
public static func write(_ value: MobileAndroidKeySpecification, into buf: inout [UInt8]) {
FfiConverterString.write(value.alias, into: &buf)
FfiConverterTypeMobileAndroidAuthorization.write(value.authorization, into: &buf)
FfiConverterBool.write(value.unlockedDevice, into: &buf)
FfiConverterBool.write(value.invalidateOnEnrollment, into: &buf)
FfiConverterBool.write(value.strongbox, into: &buf)
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeySpecification_lift(_ buf: RustBuffer) throws -> MobileAndroidKeySpecification {
return try FfiConverterTypeMobileAndroidKeySpecification.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeySpecification_lower(_ value: MobileAndroidKeySpecification) -> RustBuffer {
return FfiConverterTypeMobileAndroidKeySpecification.lower(value)
}
public struct MobileAuthenticationState: Equatable, Hashable {
public var unlocked: Bool
public var biometricUnlockEnabled: Bool
@@ -6359,6 +6984,210 @@ public func FfiConverterTypeMobileWatchSnapshotTransfer_lower(_ value: MobileWat
public enum MobileAndroidAuthorization: Equatable, Hashable {
case none
case strongBiometric
case userPresence
}
#if compiler(>=6)
extension MobileAndroidAuthorization: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidAuthorization: FfiConverterRustBuffer {
typealias SwiftType = MobileAndroidAuthorization
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidAuthorization {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .none
case 2: return .strongBiometric
case 3: return .userPresence
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: MobileAndroidAuthorization, into buf: inout [UInt8]) {
switch value {
case .none:
writeInt(&buf, Int32(1))
case .strongBiometric:
writeInt(&buf, Int32(2))
case .userPresence:
writeInt(&buf, Int32(3))
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidAuthorization_lift(_ buf: RustBuffer) throws -> MobileAndroidAuthorization {
return try FfiConverterTypeMobileAndroidAuthorization.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidAuthorization_lower(_ value: MobileAndroidAuthorization) -> RustBuffer {
return FfiConverterTypeMobileAndroidAuthorization.lower(value)
}
public
enum MobileAndroidKeystoreError: Swift.Error, Equatable, Hashable, Foundation.LocalizedError {
case MissingKey
case Invalidated
case Lockout
case Locked
case Denied
case Cancelled
case Corrupted
case UnsupportedProtection
case StrongboxUnavailable
case AlreadyExists
case Unavailable
public var errorDescription: String? {
String(reflecting: self)
}
}
#if compiler(>=6)
extension MobileAndroidKeystoreError: Sendable {}
#endif
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public struct FfiConverterTypeMobileAndroidKeystoreError: FfiConverterRustBuffer {
typealias SwiftType = MobileAndroidKeystoreError
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MobileAndroidKeystoreError {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .MissingKey
case 2: return .Invalidated
case 3: return .Lockout
case 4: return .Locked
case 5: return .Denied
case 6: return .Cancelled
case 7: return .Corrupted
case 8: return .UnsupportedProtection
case 9: return .StrongboxUnavailable
case 10: return .AlreadyExists
case 11: return .Unavailable
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: MobileAndroidKeystoreError, into buf: inout [UInt8]) {
switch value {
case .MissingKey:
writeInt(&buf, Int32(1))
case .Invalidated:
writeInt(&buf, Int32(2))
case .Lockout:
writeInt(&buf, Int32(3))
case .Locked:
writeInt(&buf, Int32(4))
case .Denied:
writeInt(&buf, Int32(5))
case .Cancelled:
writeInt(&buf, Int32(6))
case .Corrupted:
writeInt(&buf, Int32(7))
case .UnsupportedProtection:
writeInt(&buf, Int32(8))
case .StrongboxUnavailable:
writeInt(&buf, Int32(9))
case .AlreadyExists:
writeInt(&buf, Int32(10))
case .Unavailable:
writeInt(&buf, Int32(11))
}
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeystoreError_lift(_ buf: RustBuffer) throws -> MobileAndroidKeystoreError {
return try FfiConverterTypeMobileAndroidKeystoreError.lift(buf)
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
public func FfiConverterTypeMobileAndroidKeystoreError_lower(_ value: MobileAndroidKeystoreError) -> RustBuffer {
return FfiConverterTypeMobileAndroidKeystoreError.lower(value)
}
public enum MobileAppearance: Equatable, Hashable {
case system
@@ -9165,6 +9994,9 @@ enum MobileSecretError: Swift.Error, Equatable, Hashable, Foundation.LocalizedEr
case Corrupted
case UnsupportedProtection
case Unavailable
case DeviceKeyMissing
case Invalidated
case Lockout
@@ -9202,6 +10034,9 @@ public struct FfiConverterTypeMobileSecretError: FfiConverterRustBuffer {
case 6: return .Corrupted
case 7: return .UnsupportedProtection
case 8: return .Unavailable
case 9: return .DeviceKeyMissing
case 10: return .Invalidated
case 11: return .Lockout
default: throw UniffiInternalError.unexpectedEnumCase
}
@@ -9245,6 +10080,18 @@ public struct FfiConverterTypeMobileSecretError: FfiConverterRustBuffer {
case .Unavailable:
writeInt(&buf, Int32(8))
case .DeviceKeyMissing:
writeInt(&buf, Int32(9))
case .Invalidated:
writeInt(&buf, Int32(10))
case .Lockout:
writeInt(&buf, Int32(11))
}
}
}
@@ -10974,6 +11821,27 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_mobile_checksum_method_mobiletotpoperation_progress() != 30340) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_device_state() != 19346) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_create_key() != 7937) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_encrypt() != 65449) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_decrypt() != 39727) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_delete_key() != 20162) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_aliases() != 16519) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_cancel_pending() != 39774) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileclipboard_read() != 47092) {
return InitializationResult.apiChecksumMismatch
}
@@ -11010,6 +11878,9 @@ private let initializationResult: InitializationResult = {
if (uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_activate() != 64835) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_android_secret_backend() != 24373) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_authentication() != 21237) {
return InitializationResult.apiChecksumMismatch
}
@@ -11029,6 +11900,7 @@ private let initializationResult: InitializationResult = {
return InitializationResult.apiChecksumMismatch
}
uniffiCallbackInitMobileAndroidKeystore()
uniffiCallbackInitMobileClipboard()
uniffiCallbackInitMobileClock()
uniffiCallbackInitMobileSecretBackend()
@@ -11050,6 +11922,22 @@ public func uniffiEnsureIronstorageMobileInitialized() {
// swiftlint:enable all
// IronStorage diagnostic redaction; wire values and checksums remain generated.
extension MobileAndroidCiphertext: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String { "MobileAndroidCiphertext([REDACTED])" }
public var debugDescription: String { description }
}
extension MobileAndroidDeviceState: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String { "MobileAndroidDeviceState([REDACTED])" }
public var debugDescription: String { description }
}
extension MobileAndroidKeySpecification: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String { "MobileAndroidKeySpecification([REDACTED])" }
public var debugDescription: String { description }
}
extension MobileAndroidKeystoreError: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String { "MobileAndroidKeystoreError([REDACTED])" }
public var debugDescription: String { description }
}
extension MobileAuthenticationFfiError: CustomStringConvertible, CustomDebugStringConvertible {
public var description: String { "MobileAuthenticationFfiError([REDACTED])" }
public var debugDescription: String { description }
+167
View File
@@ -242,6 +242,55 @@ typedef struct UniffiForeignFutureResultVoid {
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD0
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD0
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod0)(uint64_t, RustBuffer* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD1
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD1
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod1)(uint64_t, RustBuffer, void* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD2
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD2
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod2)(uint64_t, RustBuffer, RustBuffer, RustBuffer, RustBuffer, RustBuffer* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD3
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD3
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod3)(uint64_t, RustBuffer, RustBuffer, RustBuffer, RustBuffer, RustBuffer* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD4
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD4
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod4)(uint64_t, RustBuffer, void* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD5
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD5
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod5)(uint64_t, RustBuffer* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD6
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE_METHOD6
typedef void (*UniffiCallbackInterfaceMobileAndroidKeystoreMethod6)(uint64_t, void* _Nonnull,
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_CLIPBOARD_METHOD0
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_MOBILE_CLIPBOARD_METHOD0
@@ -319,6 +368,21 @@ typedef void (*UniffiCallbackInterfaceMobileSecretBackendMethod5)(uint64_t, void
RustCallStatus *_Nonnull uniffiCallStatus
);
#endif
#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE
#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MOBILE_ANDROID_KEYSTORE
typedef struct UniffiVTableCallbackInterfaceMobileAndroidKeystore {
UniffiCallbackInterfaceFree _Nonnull uniffiFree;
UniffiCallbackInterfaceClone _Nonnull uniffiClone;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod0 _Nonnull deviceState;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod1 _Nonnull createKey;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod2 _Nonnull encrypt;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod3 _Nonnull decrypt;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod4 _Nonnull deleteKey;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod5 _Nonnull aliases;
UniffiCallbackInterfaceMobileAndroidKeystoreMethod6 _Nonnull cancelPending;
} UniffiVTableCallbackInterfaceMobileAndroidKeystore;
#endif
#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MOBILE_CLIPBOARD
#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_MOBILE_CLIPBOARD
@@ -760,6 +824,56 @@ void uniffi_ironstorage_mobile_fn_method_mobiletotpoperation_cancel(uint64_t ptr
RustBuffer uniffi_ironstorage_mobile_fn_method_mobiletotpoperation_progress(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDKEYSTORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILEANDROIDKEYSTORE
uint64_t uniffi_ironstorage_mobile_fn_clone_mobileandroidkeystore(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDKEYSTORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_FREE_MOBILEANDROIDKEYSTORE
void uniffi_ironstorage_mobile_fn_free_mobileandroidkeystore(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_INIT_CALLBACK_VTABLE_MOBILEANDROIDKEYSTORE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_INIT_CALLBACK_VTABLE_MOBILEANDROIDKEYSTORE
void uniffi_ironstorage_mobile_fn_init_callback_vtable_mobileandroidkeystore(const UniffiVTableCallbackInterfaceMobileAndroidKeystore* _Nonnull vtable
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DEVICE_STATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DEVICE_STATE
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_device_state(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_CREATE_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_CREATE_KEY
void uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_create_key(uint64_t ptr, RustBuffer specification, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_ENCRYPT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_ENCRYPT
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_encrypt(uint64_t ptr, RustBuffer alias, RustBuffer authorization, RustBuffer aad, RustBuffer value, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DECRYPT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DECRYPT
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_decrypt(uint64_t ptr, RustBuffer alias, RustBuffer authorization, RustBuffer aad, RustBuffer value, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DELETE_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_DELETE_KEY
void uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_delete_key(uint64_t ptr, RustBuffer alias, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_ALIASES
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_ALIASES
RustBuffer uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_aliases(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_CANCEL_PENDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEANDROIDKEYSTORE_CANCEL_PENDING
void uniffi_ironstorage_mobile_fn_method_mobileandroidkeystore_cancel_pending(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILECLIPBOARD
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_CLONE_MOBILECLIPBOARD
uint64_t uniffi_ironstorage_mobile_fn_clone_mobileclipboard(uint64_t handle, RustCallStatus *_Nonnull out_status
@@ -875,6 +989,11 @@ void uniffi_ironstorage_mobile_fn_free_mobileprivateruntime(uint64_t handle, Rus
void uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_activate(uint64_t ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
uint64_t uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_android_secret_backend(uint64_t ptr, uint64_t keystore, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_AUTHENTICATION
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_FN_METHOD_MOBILEPRIVATERUNTIME_AUTHENTICATION
uint64_t uniffi_ironstorage_mobile_fn_method_mobileprivateruntime_authentication(uint64_t ptr, uint64_t backend, uint64_t clock, RustCallStatus *_Nonnull out_status
@@ -1803,6 +1922,48 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobiletotpoperation_cancel(vo
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILETOTPOPERATION_PROGRESS
uint16_t uniffi_ironstorage_mobile_checksum_method_mobiletotpoperation_progress(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DEVICE_STATE
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DEVICE_STATE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_device_state(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_CREATE_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_CREATE_KEY
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_create_key(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_ENCRYPT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_ENCRYPT
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_encrypt(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DECRYPT
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DECRYPT
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_decrypt(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DELETE_KEY
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_DELETE_KEY
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_delete_key(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_ALIASES
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_ALIASES
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_aliases(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_CANCEL_PENDING
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEANDROIDKEYSTORE_CANCEL_PENDING
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileandroidkeystore_cancel_pending(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILECLIPBOARD_READ
@@ -1875,6 +2036,12 @@ uint16_t uniffi_ironstorage_mobile_checksum_method_mobilesecretbackend_unlock(vo
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ACTIVATE
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_activate(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_ANDROID_SECRET_BACKEND
uint16_t uniffi_ironstorage_mobile_checksum_method_mobileprivateruntime_android_secret_backend(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_MOBILE_CHECKSUM_METHOD_MOBILEPRIVATERUNTIME_AUTHENTICATION
@@ -0,0 +1,282 @@
//! Android OS calls; all envelopes, policy and lifecycle remain in storage.
use crate::{MobileSecretBackend, MobileSecretError, MobileSecretLocator, MobileSecretProtection};
use ironstorage::{
repository::SecretBytes,
secret_store::{
SecretLocator, SecretProtection, SecretStoreBackend, SecretStoreError, android as os,
},
};
use std::{fmt, sync::Arc};
#[derive(Clone, Copy, uniffi::Record)]
pub struct MobileAndroidDeviceState {
pub api: u32,
pub locked: bool,
pub secure: bool,
pub strong_biometric: bool,
pub strongbox: bool,
}
#[derive(Clone, Copy, uniffi::Enum)]
pub enum MobileAndroidAuthorization {
None,
StrongBiometric,
UserPresence,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileAndroidKeySpecification {
pub alias: String,
pub authorization: MobileAndroidAuthorization,
pub unlocked_device: bool,
pub invalidate_on_enrollment: bool,
pub strongbox: bool,
}
#[derive(Clone, uniffi::Record)]
pub struct MobileAndroidCiphertext {
pub nonce: Vec<u8>,
pub bytes: Vec<u8>,
}
#[derive(Clone, Copy, Debug, uniffi::Error)]
pub enum MobileAndroidKeystoreError {
MissingKey,
Invalidated,
Lockout,
Locked,
Denied,
Cancelled,
Corrupted,
UnsupportedProtection,
StrongboxUnavailable,
AlreadyExists,
Unavailable,
}
impl fmt::Display for MobileAndroidKeystoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Android Keystore operation failed")
}
}
impl std::error::Error for MobileAndroidKeystoreError {}
#[uniffi::export(with_foreign)]
pub trait MobileAndroidKeystore: Send + Sync {
fn device_state(&self) -> Result<MobileAndroidDeviceState, MobileAndroidKeystoreError>;
fn create_key(
&self,
specification: MobileAndroidKeySpecification,
) -> Result<(), MobileAndroidKeystoreError>;
fn encrypt(
&self,
alias: String,
authorization: MobileAndroidAuthorization,
aad: Vec<u8>,
value: Vec<u8>,
) -> Result<MobileAndroidCiphertext, MobileAndroidKeystoreError>;
fn decrypt(
&self,
alias: String,
authorization: MobileAndroidAuthorization,
aad: Vec<u8>,
value: MobileAndroidCiphertext,
) -> Result<Vec<u8>, MobileAndroidKeystoreError>;
fn delete_key(&self, alias: String) -> Result<(), MobileAndroidKeystoreError>;
fn aliases(&self) -> Result<Vec<String>, MobileAndroidKeystoreError>;
fn cancel_pending(&self);
}
pub(crate) struct KeystoreAdapter(pub Arc<dyn MobileAndroidKeystore>);
impl From<os::AndroidAuthorization> for MobileAndroidAuthorization {
fn from(value: os::AndroidAuthorization) -> Self {
match value {
os::AndroidAuthorization::None => Self::None,
os::AndroidAuthorization::StrongBiometric => Self::StrongBiometric,
os::AndroidAuthorization::UserPresence => Self::UserPresence,
}
}
}
impl From<MobileAndroidKeystoreError> for os::AndroidKeystoreError {
fn from(value: MobileAndroidKeystoreError) -> Self {
match value {
MobileAndroidKeystoreError::MissingKey => Self::MissingKey,
MobileAndroidKeystoreError::Invalidated => Self::Invalidated,
MobileAndroidKeystoreError::Lockout => Self::Lockout,
MobileAndroidKeystoreError::Locked => Self::Locked,
MobileAndroidKeystoreError::Denied => Self::Denied,
MobileAndroidKeystoreError::Cancelled => Self::Cancelled,
MobileAndroidKeystoreError::Corrupted => Self::Corrupted,
MobileAndroidKeystoreError::UnsupportedProtection => Self::UnsupportedProtection,
MobileAndroidKeystoreError::StrongboxUnavailable => Self::StrongboxUnavailable,
MobileAndroidKeystoreError::AlreadyExists => Self::AlreadyExists,
MobileAndroidKeystoreError::Unavailable => Self::Unavailable,
}
}
}
impl os::AndroidKeystore for KeystoreAdapter {
fn device_state(&self) -> Result<os::AndroidDeviceState, os::AndroidKeystoreError> {
self.0
.device_state()
.map(|s| os::AndroidDeviceState {
api: s.api,
locked: s.locked,
secure: s.secure,
strong_biometric: s.strong_biometric,
strongbox: s.strongbox,
})
.map_err(Into::into)
}
fn create_key(&self, s: &os::AndroidKeySpecification) -> Result<(), os::AndroidKeystoreError> {
self.0
.create_key(MobileAndroidKeySpecification {
alias: s.alias.clone(),
authorization: s.authorization.into(),
unlocked_device: s.unlocked_device,
invalidate_on_enrollment: s.invalidate_on_enrollment,
strongbox: s.strongbox,
})
.map_err(Into::into)
}
fn encrypt(
&self,
alias: &str,
authorization: os::AndroidAuthorization,
aad: &[u8],
value: &SecretBytes,
) -> Result<os::AndroidCiphertext, os::AndroidKeystoreError> {
self.0
.encrypt(
alias.into(),
authorization.into(),
aad.to_vec(),
value.expose().to_vec(),
)
.map(|v| os::AndroidCiphertext {
nonce: v.nonce,
bytes: v.bytes,
})
.map_err(Into::into)
}
fn decrypt(
&self,
alias: &str,
authorization: os::AndroidAuthorization,
aad: &[u8],
value: &os::AndroidCiphertext,
) -> Result<SecretBytes, os::AndroidKeystoreError> {
self.0
.decrypt(
alias.into(),
authorization.into(),
aad.to_vec(),
MobileAndroidCiphertext {
nonce: value.nonce.clone(),
bytes: value.bytes.clone(),
},
)
.map(SecretBytes::new)
.map_err(Into::into)
}
fn delete_key(&self, alias: &str) -> Result<(), os::AndroidKeystoreError> {
self.0.delete_key(alias.into()).map_err(Into::into)
}
fn aliases(&self) -> Result<Vec<String>, os::AndroidKeystoreError> {
self.0.aliases().map_err(Into::into)
}
fn cancel_pending(&self) {
self.0.cancel_pending();
}
}
pub(crate) struct RustSecretBackend(pub os::AndroidSecretBackend);
impl TryFrom<MobileSecretLocator> for SecretLocator {
type Error = MobileSecretError;
fn try_from(value: MobileSecretLocator) -> Result<Self, Self::Error> {
Ok(match value {
MobileSecretLocator::ApplicationBinding => Self::ApplicationBinding,
MobileSecretLocator::OpenPgpPassphrase { fingerprint } => {
Self::OpenPgpPassphrase { fingerprint }
}
MobileSecretLocator::HttpsGitCredential {
server_id,
application_id,
} => Self::HttpsGitCredential {
server_id,
application_id,
},
MobileSecretLocator::SshKeyPassphrase { fingerprint } => Self::SshKeyPassphrase {
fingerprint: ironstorage::config::SshFingerprint::parse(&fingerprint)
.map_err(|_| MobileSecretError::Corrupted)?,
},
})
}
}
impl From<MobileSecretProtection> for SecretProtection {
fn from(value: MobileSecretProtection) -> Self {
match value {
MobileSecretProtection::DeviceUnlocked => Self::DeviceUnlocked,
MobileSecretProtection::RequireUserPresence => Self::RequireUserPresence,
MobileSecretProtection::BiometryCurrentSet => Self::BiometryCurrentSet,
}
}
}
impl From<SecretStoreError> for MobileSecretError {
fn from(value: SecretStoreError) -> Self {
match value {
SecretStoreError::AlreadyExists => Self::AlreadyExists,
SecretStoreError::Missing => Self::Missing,
SecretStoreError::Locked => Self::Locked,
SecretStoreError::Denied => Self::Denied,
SecretStoreError::Cancelled => Self::Cancelled,
SecretStoreError::Corrupted => Self::Corrupted,
SecretStoreError::UnsupportedProtection => Self::UnsupportedProtection,
SecretStoreError::Unavailable => Self::Unavailable,
SecretStoreError::DeviceKeyMissing => Self::DeviceKeyMissing,
SecretStoreError::Invalidated => Self::Invalidated,
SecretStoreError::Lockout => Self::Lockout,
_ => Self::Corrupted,
}
}
}
impl MobileSecretBackend for RustSecretBackend {
fn create(
&self,
locator: MobileSecretLocator,
protection: MobileSecretProtection,
value: Vec<u8>,
) -> Result<(), MobileSecretError> {
let secret = SecretBytes::new(value);
self.0
.create(&locator.try_into()?, protection.into(), secret.expose())
.map_err(Into::into)
}
fn replace(
&self,
locator: MobileSecretLocator,
protection: MobileSecretProtection,
value: Vec<u8>,
) -> Result<(), MobileSecretError> {
let secret = SecretBytes::new(value);
self.0
.replace(&locator.try_into()?, protection.into(), secret.expose())
.map_err(Into::into)
}
fn retrieve(
&self,
locator: MobileSecretLocator,
protection: MobileSecretProtection,
) -> Result<Vec<u8>, MobileSecretError> {
self.0
.retrieve(&locator.try_into()?, protection.into())
.map(|v| v.expose().to_vec())
.map_err(Into::into)
}
fn delete(
&self,
locator: MobileSecretLocator,
protection: MobileSecretProtection,
) -> Result<(), MobileSecretError> {
self.0
.delete(&locator.try_into()?, protection.into())
.map_err(Into::into)
}
fn lock(&self) -> Result<(), MobileSecretError> {
self.0.lock().map_err(Into::into)
}
fn unlock(&self) -> Result<(), MobileSecretError> {
self.0.unlock().map_err(Into::into)
}
}
+2
View File
@@ -79,6 +79,8 @@ use ironstorage::{
uniffi::setup_scaffolding!();
mod android_keystore;
pub use android_keystore::*;
mod platform;
pub use platform::*;
+6
View File
@@ -130,6 +130,9 @@ pub enum MobileSecretError {
Corrupted,
UnsupportedProtection,
Unavailable,
DeviceKeyMissing,
Invalidated,
Lockout,
}
impl fmt::Display for MobileSecretError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -148,6 +151,9 @@ impl From<MobileSecretError> for SecretStoreError {
MobileSecretError::Corrupted => Self::Corrupted,
MobileSecretError::UnsupportedProtection => Self::UnsupportedProtection,
MobileSecretError::Unavailable => Self::Unavailable,
MobileSecretError::DeviceKeyMissing => Self::DeviceKeyMissing,
MobileSecretError::Invalidated => Self::Invalidated,
MobileSecretError::Lockout => Self::Lockout,
}
}
}
@@ -100,6 +100,17 @@ pub struct MobilePrivateRuntime {
}
#[uniffi::export]
impl MobilePrivateRuntime {
pub fn android_secret_backend(
&self,
keystore: Arc<dyn crate::MobileAndroidKeystore>,
) -> Result<Arc<dyn MobileSecretBackend>, MobilePrivateStorageFfiError> {
let backend = self
.storage
.android_secret_backend(Arc::new(crate::android_keystore::KeystoreAdapter(keystore)))?;
Ok(Arc::new(crate::android_keystore::RustSecretBackend(
backend,
)))
}
pub fn activate(&self) -> Result<(), MobilePrivateStorageFfiError> {
self.storage.activate().map_err(Into::into)
}
+1
View File
@@ -92,6 +92,7 @@ zbus-secret-service-keyring-store = { workspace = true, optional = true }
arboard = { workspace = true, optional = true }
[dev-dependencies]
aes-gcm = "0.11.1"
hex = "0.4"
keepass = { workspace = true, features = ["save_kdbx4"] }
nix = { version = "0.31", features = ["fs"] }
+6 -3
View File
@@ -107,11 +107,14 @@ impl MobileAuthenticationError {
"IronStorage Locked",
"The authentication lease expired.",
),
AuthenticationError::SecretStore(SecretStoreError::Missing) => {
Self::passphrase_required("Enter the GPG key passphrase to continue.")
}
AuthenticationError::SecretStore(
SecretStoreError::Missing
| SecretStoreError::DeviceKeyMissing
| SecretStoreError::Invalidated,
) => Self::passphrase_required("Enter the GPG key passphrase to continue."),
AuthenticationError::SecretStore(
SecretStoreError::Denied
| SecretStoreError::Lockout
| SecretStoreError::UnsupportedProtection
| SecretStoreError::Unavailable,
) => Self::new(
+14 -2
View File
@@ -275,6 +275,14 @@ impl PrivateStorage {
Ok(storage)
}
pub fn android_secret_backend(
&self,
keystore: std::sync::Arc<dyn crate::secret_store::android::AndroidKeystore>,
) -> Result<crate::secret_store::android::AndroidSecretBackend, PrivateStorageError> {
let directory = private_directory(&self.data, "secrets")?;
crate::secret_store::android::AndroidSecretBackend::open(directory, keystore)
.map_err(secret_error)
}
pub fn root(&self) -> &Path {
&self.root
}
@@ -476,8 +484,12 @@ fn secret_error(error: SecretStoreError) -> PrivateStorageError {
SecretStoreError::Locked | SecretStoreError::Denied | SecretStoreError::Cancelled => {
PrivateStorageError::SecureStorageLocked
}
SecretStoreError::Missing => PrivateStorageError::RestoredWithoutKeystore,
SecretStoreError::Corrupted => PrivateStorageError::InvalidSecureBinding,
SecretStoreError::Missing | SecretStoreError::DeviceKeyMissing => {
PrivateStorageError::RestoredWithoutKeystore
}
SecretStoreError::Corrupted | SecretStoreError::Invalidated => {
PrivateStorageError::InvalidSecureBinding
}
_ => PrivateStorageError::SecureStorageUnavailable,
}
}
+9
View File
@@ -16,6 +16,7 @@ use crate::{
repository::SecretBytes,
};
pub mod android;
mod platform;
pub(crate) use platform::NativeSecretBackend;
@@ -283,6 +284,9 @@ impl SecretCachePolicy {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SecretStoreError {
DeviceKeyMissing,
Invalidated,
Lockout,
InvalidReference,
InvalidCachePolicy,
AlreadyExists,
@@ -298,6 +302,11 @@ pub enum SecretStoreError {
impl fmt::Display for SecretStoreError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::DeviceKeyMissing => {
"the device key is missing; recover the protected secret manually"
}
Self::Invalidated => "the protected key was invalidated; recover the secret manually",
Self::Lockout => "biometric authentication is locked out",
Self::InvalidReference => "the secret reference is invalid",
Self::InvalidCachePolicy => "the secret cache policy is invalid",
Self::AlreadyExists => "the referenced secret already exists",
File diff suppressed because it is too large. Load diff
+91
View File
@@ -0,0 +1,91 @@
# Android protected secrets (#93)
`crates/storage::secret_store::android` implements the secure-secret backend.
`MobilePrivateRuntime.androidSecretBackend` connects it to a narrow UniFFI OS
callback. Core translates callback types; platform calls Android Keystore,
KeyguardManager and AndroidX BiometricPrompt. No frontend reads a password-store
repository, chooses a credential identity/protection, persists ciphertext, or
implements encryption itself. App startup verifies its existing private device
binding using this real backend before presenting the Rust shell.
The OS holds non-exportable AES-256-GCM keys. Rust stores versioned sealed
records under `noBackupFilesDir/ironstorage/secrets`, with 0700 directories and
0600 files. Service/account identities become SHA-256 names using
length-delimited namespace fields; OpenPGP fingerprints normalize to uppercase.
Each write creates a random generation-specific alias. GCM authenticates the
record version, identity, protection class and complete alias; its nonce is the
12-byte IV generated by the OS. Authentication happens before supplying AAD to
an authorization-bound cipher. No plaintext token, account record or passphrase
is serialized in ordinary settings, the layout marker or logs.
| Storage policy | Android key and operation |
| --- | --- |
| DeviceUnlocked (Git credentials/device binding) | No interactive authentication; Keyguard checks before and after each operation, plus `setUnlockedDeviceRequired` from API 28 |
| BiometryCurrentSet (optional enrolled GPG passphrase) | Per-operation `BIOMETRIC_STRONG` CryptoObject, enrollment invalidation enabled, secure lock required; no PIN fallback |
| RequireUserPresence | From API 30, per-operation `BIOMETRIC_STRONG \| DEVICE_CREDENTIAL` CryptoObject; the configured credential flow is explicit |
API 26–27 use the OS locked-state checks for DeviceUnlocked and the legacy
per-operation biometric key parameter. API 28–34 require a secure screen lock
because those releases have documented unlocked-key bugs. API 35+ support
unlocked keys without a secure lock. Unsupported protection returns a typed
error; it never downgrades an enrolled passphrase to device-only protection.
Rust restricts OpenPGP locators to the two interactive protection classes and
Git/device-binding locators to DeviceUnlocked. Factory failures become storage
recovery state in Rust; Kotlin only presents that state.
Android invalidates biometric-bound keys when a biometric is added or all
biometrics are removed; removing a secure lock also invalidates authentication
keys. Removing one of several original enrolled biometrics does not grant a new
biometric access. These are the [Android key-policy guarantees](https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder),
not a claim of extra OS enrollment APIs or physical-device validation.
StrongBox is requested only when the framework advertises it. A typed
StrongBox-unavailable generation failure retries the same specification using
ordinary Keystore; authentication and unlocked-device requirements stay exact.
Other generation/cipher failures never trigger a weaker key or a retry without
authentication. The persistent emulators exercise the non-StrongBox path; Rust
mocks exercise the advertised-but-unavailable fallback.
A private advisory lock serializes record transactions. Create rejects an
existing record; replace rejects an absent one. Replacement seals with a new
key, syncs a private atomic file, renames it and syncs the directory before
retiring the previous key. Cipher/authentication/write failure preserves the
previous record and removes the candidate key. If durability fails after rename,
the new key is retained because the new record may already be installed.
Delete unlinks the selected sealed record and deletes its key without requiring
biometric authentication, so disabling enrollment can remove protected data.
Startup removes only well-formed owned orphan aliases and unlocked owned atomic
files, retaining keys when any record cannot be parsed. Unrelated aliases are
left alone. Records are bounded to 16 KiB and startup enumeration is bounded.
Missing ciphertext, missing device keys, invalidated keys, corruption,
duplicate, cancellation, denial, device lock, lockout, unsupported policy and
unavailability have separate Rust-visible errors. Existing bound data with a
missing Keystore key reports restore recovery and never receives a replacement
binding. Tampering or changing the requested policy fails closed. Background
lock increments the Rust generation and cancels the pending OS prompt; a late
cipher result cannot return plaintext for an obsolete generation.
Kotlin uses the exact authorized Cipher returned in the CryptoObject callback,
one pending request, a bounded wait and cancellation. OS input arrays are
zeroed in `finally`; generated foreign callback return arrays are erased after
serialization. Rust immediately owns cleartext in `SecretBytes`, including
incoming create/replace buffers. Errors and generated diagnostics are redacted.
The JVM, OS cipher implementation and UniFFI serialization can still make
transient copies; this is not a guarantee of complete memory erasure. There is
no EncryptedSharedPreferences, MasterKey, alternative repository format or
application subprocess.
Tests use public fixture secrets and synthetic emulator fingers. Rust exercises
all record operations, policy mappings, failed replacement, tampering, restore,
orphan ownership, lock/generation races, shared credential encoding and actual
mobile biometric-disable deletion. Android instrumentation uses real Keystore
keys and real CryptoObject prompts; its host is confined to the test APK.
Enrollment/lock removal deliberately invalidate only disposable fixture keys.
Android 35 delivers the direct lockout callback, verified as `Lockout`. Android
37 instead offers an OS PIN screen to recover the sensor during biometric-only
authentication. The emulator test enters that PIN and verifies the operation
still requires a matching fingerprint; cancelling then returns `Cancelled` and
no cipher result. The PIN is never added to the biometric-only key's permitted
authenticators. Native callback tests also verify Rust-visible lockout, policy
rejection, factory recovery mapping and post-serialization array erasure.
+1 -1
View File
@@ -65,7 +65,7 @@ milestone. This list gives ownership, not permission to skip acceptance criteria
| [#90 Private paths/config/cache/backup](https://git.rfc1437.de/hugo/IronStorage/issues/90) | crates/storage plus Android path adapter | Context roots/capabilities, atomic layout/migrations, backup rules; Rust and sandbox/update tests |
| [#91 Platform-neutral mobile contract](https://git.rfc1437.de/hugo/IronStorage/issues/91) | crates/storage and shared UniFFI bridge | Typed semantic actions/icons, opt-in Watch data, platform callbacks, shared Swift/Kotlin inventory tests |
| [#92 Native Rust/UniFFI packaging](https://git.rfc1437.de/hugo/IronStorage/issues/92) | Shared/Android Rust bridge and Gradle rust-bridge | cargo-ndk, generated immutable Kotlin/JNA Android, three ABIs, checksums/16 KB/loading/debug symbols |
| [#93 Keystore adapter](https://git.rfc1437.de/hugo/IronStorage/issues/93) | crates/storage secure-secret orchestration and platform adapter | Framework Keystore AES-GCM; callback facts, sealed records, invalidation/recovery tests |
| [#93 Keystore adapter](https://git.rfc1437.de/hugo/IronStorage/issues/93) | Delivered: Rust sealed-record orchestration and Android OS adapter | [Keystore policy and recovery](keystore.md); native AES-GCM/CryptoObject and Rust lifecycle tests |
| [#94 CI/static analysis/reproducibility](https://git.rfc1437.de/hugo/IronStorage/issues/94) | Build/CI/test infrastructure | Cargo gates, Gradle build/Lint/ktlint, JVM/Compose/instrumented tests, verification/locks/SBOM/ABI checks |
| [#95 Local-first/HTTPS onboarding](https://git.rfc1437.de/hugo/IronStorage/issues/95) | storage mobile_onboarding plus Compose features | Existing Rust initialization/discovery/clone/credentials; no Kotlin transport |
| [#96 Hierarchical navigation](https://git.rfc1437.de/hugo/IronStorage/issues/96) | storage mobile_passwords plus Compose features | Material 3 Adaptive list/detail, typed rows/stable IDs |
+4 -4
View File
@@ -17,7 +17,7 @@ activation fail instead of consulting HOME, cwd, XDG or public storage.
| version / device binding | `noBackupFilesDir/ironstorage/layout.toml` | Atomic version 1 marker; unknown versions require a compatible migration |
| transactional scratch | `noBackupFilesDir/ironstorage/scratch` | Per-owner file locks; startup removes orphaned transactions and skips live owners |
| disposable cache | `cacheDir/ironstorage` | Only this capability is cleared by `clearDisposableCache`; vault/key/catalog data is retained |
| protected binding and credentials | OS secure backend / no-backup ciphertext (#93) | No serialized token or passphrase in configuration or layout metadata |
| protected binding and credentials | Keystore AES-GCM keys / `noBackupFilesDir/ironstorage/secrets` (#93) | No serialized token or passphrase in configuration or layout metadata |
| `filesDir` | OS root fact only | No durable password-store object is placed here |
Directory opens use safe Rust `rustix` handles with `NOFOLLOW`; names are single
@@ -45,9 +45,9 @@ links without following their targets and cannot descend into the vault.
persists only its SHA-256 in layout metadata. A missing or mismatched protected
record for an already bound layout reports `RestoredWithoutKeystore` or
`InvalidSecureBinding`; it never creates a replacement that would conceal key
loss. Locked/unavailable storage has separate recovery kinds. #93 supplies the
real Android Keystore adapter; the contract already executes through the
injected backend and is tested with loss/mismatch fixtures.
loss. Locked/unavailable storage has separate recovery kinds. #93 supplies the [real Android Keystore adapter](keystore.md); startup verifies
the binding through it before presenting the shell. The contract is tested with
loss/mismatch fixtures.
Errors distinguish full storage, read-only storage, access denial, missing
roots, concurrent initialization, corrupt configuration/layout and unsupported
+23
View File
@@ -61,6 +61,14 @@ fn harden_source(source: &str) -> Result<String, Box<dyn Error>> {
}
let source = source.replace(internal, "class InternalException(@Suppress(\"UNUSED_PARAMETER\") message: String) : kotlin.Exception(\"Native Rust failure ([REDACTED])\")")
.replace(callback, "val err = \"Native callback failed ([REDACTED])\"");
// Callback results are operation-owned; erase plaintext after copying into the wire buffer.
let returned_secret = "val writeReturn = { value: kotlin.ByteArray -> uniffiOutReturn.setValue(FfiConverterByteArray.lower(value)) }";
if source.contains("internal object uniffiCallbackInterfaceMobileAndroidKeystore")
&& source.matches(returned_secret).count() != 2
{
return Err("UniFFI secret callback template changed; review byte-array lifetime".into());
}
let source = source.replace(returned_secret, "val writeReturn = { value: kotlin.ByteArray -> try { uniffiOutReturn.setValue(FfiConverterByteArray.lower(value)) } finally { value.fill(0) } }");
let records = Regex::new(r"(?ms)^([ \t]*)data class (\w+)\s*\((.*?)\)[^\{]*\{\n")?;
if !records.is_match(&source) {
return Err("UniFFI record template changed; review diagnostic hardening".into());
@@ -128,6 +136,21 @@ mod tests {
assert!(!hardened.contains("kotlin.Exception(message)"));
}
#[test]
fn erases_owned_callback_results_after_serialization_even_on_failure() {
let source = SOURCE.to_owned()
+ "val writeReturn = { value: kotlin.ByteArray -> uniffiOutReturn.setValue(FfiConverterByteArray.lower(value)) }";
let hardened = harden_source(&source).unwrap();
assert!(hardened.contains("finally { value.fill(0) }"));
assert!(hardened.contains("FfiConverterByteArray.lower(value)"));
assert!(
harden_source(
&(SOURCE.to_owned()
+ "internal object uniffiCallbackInterfaceMobileAndroidKeystore")
)
.is_err()
);
}
#[test]
fn fails_closed_if_upstream_templates_or_integrity_checks_change() {
assert!(harden_source("data class Secret(val value: String)").is_err());