aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-02 18:01:43 +0100
committerDanilo M. <danix@danix.xyz>2026-11-02 18:01:43 +0100
commit95ebfbe95e733456dff4c53ced3cf2e17ae991c1 (patch)
tree50e43d78a5b065554302aef7be5e098715ee1dfb /app
parent253f82e2dcb06619b54aca0cc4afc39735810e4c (diff)
downloadHardenedPass-95ebfbe95e733456dff4c53ced3cf2e17ae991c1.tar.gz
HardenedPass-95ebfbe95e733456dff4c53ced3cf2e17ae991c1.zip
fix(ssh): unlock screen-lock protected SSH keys with a fingerprint
A Keystore SSH key generated with "Protect with screen lock credential" was bound to AUTH_DEVICE_CREDENTIAL only, while the unlock prompt also accepted biometrics. A fingerprint passed the prompt, then signing the SSH auth request threw UserNotAuthenticatedException, sshj logged "User not authenticated" and the clone failed with "Socket closed". Bind new keys to AUTH_DEVICE_CREDENTIAL or AUTH_BIOMETRIC_STRONG, and allow only strong biometrics in the prompt on Android 11+, since weak ones never unlock Keystore keys (the androidx MasterKey keys already use credential or strong biometric). Android 9 and 10 cannot combine BIOMETRIC_STRONG with DEVICE_CREDENTIAL, so they keep the weak class. Existing protected keys still need the PIN until regenerated. Verified on the emulator (Class 3 fingerprint): a protected ECDSA key unlocked with a fingerprint authenticates and clones over SSH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app')
-rw-r--r--app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt15
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt5
2 files changed, 17 insertions, 3 deletions
diff --git a/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt b/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt
index 9458e8f4..4aad4588 100644
--- a/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt
+++ b/app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt
@@ -5,6 +5,7 @@
package app.passwordstore.util.auth
import android.app.KeyguardManager
+import android.os.Build
import androidx.annotation.StringRes
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
@@ -18,8 +19,18 @@ import logcat.logcat
object BiometricAuthenticator {
private const val TAG = "BiometricAuthenticator"
- private const val VALID_AUTHENTICATORS =
- Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK
+
+ /**
+ * Keystore keys that require user authentication are unlocked only by the device credential or a
+ * Class 3 (strong) biometric, so a weak biometric would pass the prompt and then fail to sign or
+ * decrypt with `User not authenticated`. Android 9 and 10 do not support combining
+ * [Authenticators.BIOMETRIC_STRONG] with [Authenticators.DEVICE_CREDENTIAL], so they keep the
+ * weak class.
+ */
+ private val VALID_AUTHENTICATORS =
+ Authenticators.DEVICE_CREDENTIAL or
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Authenticators.BIOMETRIC_STRONG
+ else Authenticators.BIOMETRIC_WEAK
/**
* Sealed class to wrap [BiometricPrompt]'s [Int]-based return codes into more easily-interpreted
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt b/app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt
index a7127ec6..99e600bf 100644
--- a/app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt
+++ b/app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt
@@ -282,7 +282,10 @@ object SshKey {
if (requireAuthentication) {
setUserAuthenticationRequired(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- setUserAuthenticationParameters(30, KeyProperties.AUTH_DEVICE_CREDENTIAL)
+ setUserAuthenticationParameters(
+ 30,
+ KeyProperties.AUTH_DEVICE_CREDENTIAL or KeyProperties.AUTH_BIOMETRIC_STRONG,
+ )
} else {
@Suppress("DEPRECATION") setUserAuthenticationValidityDurationSeconds(30)
}