diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-02 18:01:43 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-02 18:01:43 +0100 |
| commit | 95ebfbe95e733456dff4c53ced3cf2e17ae991c1 (patch) | |
| tree | 50e43d78a5b065554302aef7be5e098715ee1dfb /app/src | |
| parent | 253f82e2dcb06619b54aca0cc4afc39735810e4c (diff) | |
| download | HardenedPass-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/src')
| -rw-r--r-- | app/src/main/java/app/passwordstore/util/auth/BiometricAuthenticator.kt | 15 | ||||
| -rw-r--r-- | app/src/main/java/app/passwordstore/util/git/sshj/SshKey.kt | 5 |
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) } |
