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 1fd5f9e5..188891bd 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 146fdb43..4d961137 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,18 +34,48 @@ 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 { - private var isRetry = false - private var shouldRetry = true - abstract fun askForPassword(cont: Continuation, isRetry: Boolean) + private var isRetry = false + private var shouldRetry = true + private var lastPassword: CharArray? = null + + 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) @@ -52,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) @@ -67,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 { @@ -105,9 +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.resetForReuse() } is SshAuthData.PublicKeyFile -> { ssh.authPublickey(username, ssh.loadKeys(authData.keyFile.absolutePath, authData.passphraseFinder)) + 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 ed91eede..4820d8bf 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)