Implement Rust-owned Android Keystore secure storage (#93)
Dependency security audit / rustsec (push) Failing after 23s
Dependency security audit / rustsec (push) Failing after 23s
This commit is contained in:
1 parent
874c8139a4
commit
1abd75282e
36 files changed
+4380
-37
No files matched your search
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
+359
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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>
|
||||
+306
@@ -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>
|
||||
Reference in new issue
Block a user