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
@@ -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()
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user