From 7b8196b27b8e52e6832669fa446e233a5a8313e4 Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Wed, 3 Jun 2020 08:46:36 +0200 Subject: [PATCH 1/2] Reset PasswordFinder retry state after authentication --- .../com/zeapo/pwdstore/git/config/SshjSessionFactory.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt index 146fdb4349..6a3e71fb04 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt @@ -39,10 +39,15 @@ sealed class SshAuthData { abstract class InteractivePasswordFinder : PasswordFinder { + abstract fun askForPassword(cont: Continuation, isRetry: Boolean) + private var isRetry = false private var shouldRetry = true - abstract fun askForPassword(cont: Continuation, isRetry: Boolean) + fun reset() { + isRetry = false + shouldRetry = true + } final override fun reqPassword(resource: Resource<*>?): CharArray { val password = runBlocking(Dispatchers.Main) { @@ -105,9 +110,11 @@ private class SshjSession(private val uri: URIish, private val username: String, when (authData) { is SshAuthData.Password -> { ssh.authPassword(username, authData.passwordFinder) + authData.passwordFinder.reset() } is SshAuthData.PublicKeyFile -> { ssh.authPublickey(username, ssh.loadKeys(authData.keyFile.absolutePath, authData.passphraseFinder)) + authData.passphraseFinder.reset() } } return this From 645a60300d2c463b25feb9ca576935c35e1dc239 Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Wed, 3 Jun 2020 09:31:46 +0200 Subject: [PATCH 2/2] Memorize password inbetween Git commands --- .../com/zeapo/pwdstore/git/GitAsyncTask.kt | 2 + .../pwdstore/git/config/SshjSessionFactory.kt | 42 ++++++++++++++++--- .../com/zeapo/pwdstore/utils/Extensions.kt | 6 +++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt index 1fd5f9e5c9..188891bd79 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt @@ -12,6 +12,7 @@ import android.os.AsyncTask import com.github.ajalt.timberkt.e import com.zeapo.pwdstore.PasswordStore import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.git.config.SshjSessionFactory import net.schmizz.sshj.userauth.UserAuthException import org.eclipse.jgit.api.CommitCommand import org.eclipse.jgit.api.GitCommand @@ -147,6 +148,7 @@ class GitAsyncTask( if (refreshListOnEnd) { (activity as? PasswordStore)?.resetPasswordList() } + (SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials() SshSessionFactory.setInstance(null) } diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt index 6a3e71fb04..4d961137e6 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt @@ -7,6 +7,7 @@ package com.zeapo.pwdstore.git.config import android.util.Base64 import com.github.ajalt.timberkt.d import com.github.ajalt.timberkt.w +import com.zeapo.pwdstore.utils.clear import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import net.schmizz.sshj.SSHClient @@ -33,8 +34,19 @@ import kotlin.coroutines.Continuation import kotlin.coroutines.suspendCoroutine sealed class SshAuthData { - class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData() - class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData() + class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData() { + override fun clearCredentials() { + passwordFinder.clearPassword() + } + } + + class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData() { + override fun clearCredentials() { + passphraseFinder.clearPassword() + } + } + + abstract fun clearCredentials() } abstract class InteractivePasswordFinder : PasswordFinder { @@ -43,13 +55,27 @@ abstract class InteractivePasswordFinder : PasswordFinder { private var isRetry = false private var shouldRetry = true + private var lastPassword: CharArray? = null - fun reset() { + fun resetForReuse() { isRetry = false shouldRetry = true } + fun clearPassword() { + lastPassword?.clear() + lastPassword = null + } + final override fun reqPassword(resource: Resource<*>?): CharArray { + if (lastPassword != null && !isRetry) { + // This instance successfully authenticated in a previous authentication step and is + // now being reused for a new one. We try the previous password so that the user + // does not have to type it again. + isRetry = true + return lastPassword!! + } + clearPassword() val password = runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) @@ -57,7 +83,7 @@ abstract class InteractivePasswordFinder : PasswordFinder { } isRetry = true return if (password != null) { - password.toCharArray() + password.toCharArray().also { lastPassword = it } } else { shouldRetry = false CharArray(0) @@ -72,6 +98,10 @@ class SshjSessionFactory(private val username: String, private val authData: Ssh override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession { return SshjSession(uri, username, authData, hostKeyFile).connect() } + + fun clearCredentials() { + authData.clearCredentials() + } } private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier { @@ -110,11 +140,11 @@ private class SshjSession(private val uri: URIish, private val username: String, when (authData) { is SshAuthData.Password -> { ssh.authPassword(username, authData.passwordFinder) - authData.passwordFinder.reset() + authData.passwordFinder.resetForReuse() } is SshAuthData.PublicKeyFile -> { ssh.authPublickey(username, ssh.loadKeys(authData.keyFile.absolutePath, authData.passphraseFinder)) - authData.passphraseFinder.reset() + authData.passphraseFinder.resetForReuse() } } return this diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt index ed91eede83..4820d8bffd 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt @@ -26,6 +26,12 @@ fun String.splitLines(): Array { return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() } +fun CharArray.clear() { + forEachIndexed { i, _ -> + this[i] = 0.toChar() + } +} + fun Context.resolveAttribute(attr: Int): Int { val typedValue = TypedValue() this.theme.resolveAttribute(attr, typedValue, true)