Support multiple authentication methods (#825)
* Offer password SSH authentication after publickey * git: re-add back button handling Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Hide unsupported authentication methods Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * GitCommandExecutor: cleanup and address build warning Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Address review comments Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * DecryptActivity: hide menu items until decrypt finishes Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Add changelog entry Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
ff780b02de
commit
9e0fb93f91
4 changed files with 32 additions and 28 deletions
|
@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Add [Bromite](https://www.bromite.org/) and [Ungoogled Chromium](https://git.droidware.info/wchen342/ungoogled-chromium-android) to supported browsers list for Autofill
|
||||
- Add ability to view the Git commit log
|
||||
- Allow generating ECDSA and ED25519 keys for SSH
|
||||
- Add support for multiple/fallback authentication methods for SSH
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import com.zeapo.pwdstore.UserPreference
|
|||
import com.zeapo.pwdstore.git.GitCommandExecutor
|
||||
import com.zeapo.pwdstore.git.config.AuthMode
|
||||
import com.zeapo.pwdstore.git.config.GitSettings
|
||||
import com.zeapo.pwdstore.git.sshj.SshAuthData
|
||||
import com.zeapo.pwdstore.git.sshj.SshAuthMethod
|
||||
import com.zeapo.pwdstore.git.sshj.SshKey
|
||||
import com.zeapo.pwdstore.git.sshj.SshjSessionFactory
|
||||
import com.zeapo.pwdstore.utils.BiometricAuthenticator
|
||||
|
@ -99,8 +99,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun registerAuthProviders(authData: SshAuthData, credentialsProvider: CredentialsProvider? = null) {
|
||||
sshSessionFactory = SshjSessionFactory(authData, hostKeyFile)
|
||||
private fun registerAuthProviders(authMethod: SshAuthMethod, credentialsProvider: CredentialsProvider? = null) {
|
||||
sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile)
|
||||
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
|
||||
command.setTransportConfigCallback { transport: Transport ->
|
||||
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
|
||||
|
@ -154,8 +154,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
|||
}
|
||||
when (result) {
|
||||
is BiometricAuthenticator.Result.Success -> {
|
||||
registerAuthProviders(
|
||||
SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey)))
|
||||
registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
|
||||
}
|
||||
is BiometricAuthenticator.Result.Cancelled -> {
|
||||
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
|
||||
|
@ -173,7 +172,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
registerAuthProviders(SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey)))
|
||||
registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
|
||||
}
|
||||
} else {
|
||||
onMissingSshKeyFile()
|
||||
|
@ -181,13 +180,10 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
|
|||
// error, allowing users to make the SSH key selection.
|
||||
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
|
||||
}
|
||||
AuthMode.OpenKeychain -> registerAuthProviders(SshAuthData.OpenKeychain(callingActivity))
|
||||
AuthMode.OpenKeychain -> registerAuthProviders(SshAuthMethod.OpenKeychain(callingActivity))
|
||||
AuthMode.Password -> {
|
||||
val credentialFinder = CredentialFinder(callingActivity, AuthMode.Password)
|
||||
val httpsCredentialProvider = HttpsCredentialsProvider(credentialFinder)
|
||||
registerAuthProviders(
|
||||
SshAuthData.Password(CredentialFinder(callingActivity, AuthMode.Password)),
|
||||
httpsCredentialProvider)
|
||||
val httpsCredentialProvider = HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password))
|
||||
registerAuthProviders(SshAuthMethod.Password(callingActivity), httpsCredentialProvider)
|
||||
}
|
||||
AuthMode.None -> {
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ import org.openintents.ssh.authentication.response.Response
|
|||
import org.openintents.ssh.authentication.response.SigningResponse
|
||||
import org.openintents.ssh.authentication.response.SshPublicKeyResponse
|
||||
|
||||
class OpenKeychainKeyProvider private constructor(private val activity: FragmentActivity) : KeyProvider, Closeable {
|
||||
class OpenKeychainKeyProvider private constructor(activity: FragmentActivity) : KeyProvider, Closeable {
|
||||
|
||||
companion object {
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ import com.github.ajalt.timberkt.d
|
|||
import com.github.ajalt.timberkt.w
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import com.github.michaelbull.result.runCatching
|
||||
import com.zeapo.pwdstore.git.config.AuthMode
|
||||
import com.zeapo.pwdstore.git.operation.CredentialFinder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
@ -28,6 +30,8 @@ import net.schmizz.sshj.common.SecurityUtils
|
|||
import net.schmizz.sshj.connection.channel.direct.Session
|
||||
import net.schmizz.sshj.transport.verification.FingerprintVerifier
|
||||
import net.schmizz.sshj.transport.verification.HostKeyVerifier
|
||||
import net.schmizz.sshj.userauth.method.AuthPassword
|
||||
import net.schmizz.sshj.userauth.method.AuthPublickey
|
||||
import net.schmizz.sshj.userauth.password.PasswordFinder
|
||||
import net.schmizz.sshj.userauth.password.Resource
|
||||
import org.eclipse.jgit.transport.CredentialsProvider
|
||||
|
@ -36,10 +40,10 @@ import org.eclipse.jgit.transport.SshSessionFactory
|
|||
import org.eclipse.jgit.transport.URIish
|
||||
import org.eclipse.jgit.util.FS
|
||||
|
||||
sealed class SshAuthData {
|
||||
class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData()
|
||||
class SshKey(val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
|
||||
class OpenKeychain(val activity: FragmentActivity) : SshAuthData()
|
||||
sealed class SshAuthMethod(val activity: FragmentActivity) {
|
||||
class Password(activity: FragmentActivity) : SshAuthMethod(activity)
|
||||
class SshKey(activity: FragmentActivity) : SshAuthMethod(activity)
|
||||
class OpenKeychain(activity: FragmentActivity) : SshAuthMethod(activity)
|
||||
}
|
||||
|
||||
abstract class InteractivePasswordFinder : PasswordFinder {
|
||||
|
@ -62,12 +66,12 @@ abstract class InteractivePasswordFinder : PasswordFinder {
|
|||
final override fun shouldRetry(resource: Resource<*>?) = true
|
||||
}
|
||||
|
||||
class SshjSessionFactory(private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
|
||||
class SshjSessionFactory(private val authMethod: SshAuthMethod, private val hostKeyFile: File) : SshSessionFactory() {
|
||||
|
||||
private var currentSession: SshjSession? = null
|
||||
|
||||
override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession {
|
||||
return currentSession ?: SshjSession(uri, uri.user, authData, hostKeyFile).connect().also {
|
||||
return currentSession ?: SshjSession(uri, uri.user, authMethod, hostKeyFile).connect().also {
|
||||
d { "New SSH connection created" }
|
||||
currentSession = it
|
||||
}
|
||||
|
@ -100,7 +104,7 @@ private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier {
|
|||
}
|
||||
}
|
||||
|
||||
private class SshjSession(uri: URIish, private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : RemoteSession {
|
||||
private class SshjSession(uri: URIish, private val username: String, private val authMethod: SshAuthMethod, private val hostKeyFile: File) : RemoteSession {
|
||||
|
||||
private lateinit var ssh: SSHClient
|
||||
private var currentCommand: Session? = null
|
||||
|
@ -124,17 +128,20 @@ private class SshjSession(uri: URIish, private val username: String, private val
|
|||
ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
|
||||
if (!ssh.isConnected)
|
||||
throw IOException()
|
||||
when (authData) {
|
||||
is SshAuthData.Password -> {
|
||||
ssh.authPassword(username, authData.passwordFinder)
|
||||
val passwordAuth = AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password))
|
||||
when (authMethod) {
|
||||
is SshAuthMethod.Password -> {
|
||||
ssh.auth(username, passwordAuth)
|
||||
}
|
||||
is SshAuthData.SshKey -> {
|
||||
ssh.authPublickey(username, SshKey.provide(ssh, authData.passphraseFinder))
|
||||
is SshAuthMethod.SshKey -> {
|
||||
val pubkeyAuth = AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)))
|
||||
ssh.auth(username, pubkeyAuth, passwordAuth)
|
||||
}
|
||||
is SshAuthData.OpenKeychain -> {
|
||||
is SshAuthMethod.OpenKeychain -> {
|
||||
runBlocking {
|
||||
OpenKeychainKeyProvider.prepareAndUse(authData.activity) { provider ->
|
||||
ssh.authPublickey(username, provider)
|
||||
OpenKeychainKeyProvider.prepareAndUse(authMethod.activity) { provider ->
|
||||
val openKeychainAuth = AuthPublickey(provider)
|
||||
ssh.auth(username, openKeychainAuth, passwordAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue