Enforce Android lifecycle and presentation security (#100)
Android ARM verification / rust (push) Failing after 47m6s
Android ARM verification / android (push) Failing after 8s
Dependency security audit / rustsec (push) Successful in 23s

This commit is contained in:
hugo committed 2026-10-09 18:59:18 +02:00
1 parent c0ba6d43d8
commit a06f39af23
37 files changed
+1129 -110

No files matched your search

@@ -39,6 +39,7 @@ class AndroidKeystoreAdapter(context: Context) : AndroidKeystore {
private val handler = Handler(Looper.getMainLooper())
private var activity = WeakReference<FragmentActivity>(null)
private val pending = AtomicReference<Pending?>()
val authenticationPending: Boolean get() = pending.get() != null
fun attach(host: FragmentActivity) {
activity = WeakReference(host)
@@ -1,29 +1,61 @@
package de.rfc1437.ironstorage.platform
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.PersistableBundle
import de.rfc1437.ironstorage.core.SecretClipboard
import java.io.IOException
import java.util.UUID
class AndroidSecretClipboard(context: Context) : SecretClipboard {
class AndroidSecretClipboard(private val context: Context) : SecretClipboard {
private val manager = context.getSystemService(ClipboardManager::class.java)
override fun read(): ByteArray? = manager.primaryClip?.let { clip ->
if (clip.itemCount == 1) clip.getItemAt(0).text?.toString()?.toByteArray() else null
private val identity = UUID.randomUUID().toString()
private var timestamp: Long? = null
override fun beginCopy(): SecretClipboard = AndroidSecretClipboard(context)
private fun owns(clip: ClipData) = clip.description.extras?.getString(OWNER) == identity &&
timestamp != null && clip.description.timestamp == timestamp
override fun read(): ByteArray? = access {
manager.primaryClip?.takeIf(::owns)?.let { clip ->
if (clip.itemCount == 1) clip.getItemAt(0).text?.toString()?.toByteArray() else null
}
}
override fun write(value: ByteArray) {
override fun write(value: ByteArray) = access {
val clip = ClipData.newPlainText("IronStorage", value.toString(Charsets.UTF_8))
clip.description.extras = PersistableBundle().apply {
putBoolean("android.content.extra.IS_SENSITIVE", true)
putBoolean(
if (Build.VERSION.SDK_INT >=
33
) {
ClipDescription.EXTRA_IS_SENSITIVE
} else {
"android.content.extra.IS_SENSITIVE"
},
true
)
putString(OWNER, identity)
}
manager.setPrimaryClip(clip)
timestamp = manager.primaryClipDescription?.takeIf {
it.extras?.getString(OWNER) == identity
}?.timestamp
}
override fun clear() {
override fun clear() = access {
if (manager.primaryClip?.let(::owns) != true) return@access
if (Build.VERSION.SDK_INT >= 28) {
manager.clearPrimaryClip()
} else {
manager.setPrimaryClip(ClipData.newPlainText("", ""))
}
}
private fun <T> access(block: () -> T): T = try {
block()
} catch (_: RuntimeException) {
throw IOException("Android clipboard access is unavailable.")
}
private companion object {
const val OWNER = "de.rfc1437.ironstorage.clipboard-owner"
}
}