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:
Fabian Henneke
2020-09-08 12:08:06 +02:00
committed by GitHub
parent ff780b02de
commit 9e0fb93f91
4 changed files with 32 additions and 28 deletions

View File

@@ -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 [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 - Add ability to view the Git commit log
- Allow generating ECDSA and ED25519 keys for SSH - Allow generating ECDSA and ED25519 keys for SSH
- Add support for multiple/fallback authentication methods for SSH
### Changed ### Changed

View File

@@ -19,7 +19,7 @@ import com.zeapo.pwdstore.UserPreference
import com.zeapo.pwdstore.git.GitCommandExecutor import com.zeapo.pwdstore.git.GitCommandExecutor
import com.zeapo.pwdstore.git.config.AuthMode import com.zeapo.pwdstore.git.config.AuthMode
import com.zeapo.pwdstore.git.config.GitSettings 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.SshKey
import com.zeapo.pwdstore.git.sshj.SshjSessionFactory import com.zeapo.pwdstore.git.sshj.SshjSessionFactory
import com.zeapo.pwdstore.utils.BiometricAuthenticator 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) { private fun registerAuthProviders(authMethod: SshAuthMethod, credentialsProvider: CredentialsProvider? = null) {
sshSessionFactory = SshjSessionFactory(authData, hostKeyFile) sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile)
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command -> commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
command.setTransportConfigCallback { transport: Transport -> command.setTransportConfigCallback { transport: Transport ->
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory (transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
@@ -154,8 +154,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
} }
when (result) { when (result) {
is BiometricAuthenticator.Result.Success -> { is BiometricAuthenticator.Result.Success -> {
registerAuthProviders( registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey)))
} }
is BiometricAuthenticator.Result.Cancelled -> { is BiometricAuthenticator.Result.Cancelled -> {
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)) return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
@@ -173,7 +172,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
} }
} }
} else { } else {
registerAuthProviders(SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey))) registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
} }
} else { } else {
onMissingSshKeyFile() onMissingSshKeyFile()
@@ -181,13 +180,10 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
// error, allowing users to make the SSH key selection. // error, allowing users to make the SSH key selection.
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)) return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
} }
AuthMode.OpenKeychain -> registerAuthProviders(SshAuthData.OpenKeychain(callingActivity)) AuthMode.OpenKeychain -> registerAuthProviders(SshAuthMethod.OpenKeychain(callingActivity))
AuthMode.Password -> { AuthMode.Password -> {
val credentialFinder = CredentialFinder(callingActivity, AuthMode.Password) val httpsCredentialProvider = HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password))
val httpsCredentialProvider = HttpsCredentialsProvider(credentialFinder) registerAuthProviders(SshAuthMethod.Password(callingActivity), httpsCredentialProvider)
registerAuthProviders(
SshAuthData.Password(CredentialFinder(callingActivity, AuthMode.Password)),
httpsCredentialProvider)
} }
AuthMode.None -> { AuthMode.None -> {
} }

View File

@@ -39,7 +39,7 @@ import org.openintents.ssh.authentication.response.Response
import org.openintents.ssh.authentication.response.SigningResponse import org.openintents.ssh.authentication.response.SigningResponse
import org.openintents.ssh.authentication.response.SshPublicKeyResponse 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 { companion object {

View File

@@ -10,6 +10,8 @@ import com.github.ajalt.timberkt.d
import com.github.ajalt.timberkt.w import com.github.ajalt.timberkt.w
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.runCatching 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.File
import java.io.IOException import java.io.IOException
import java.io.InputStream 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.connection.channel.direct.Session
import net.schmizz.sshj.transport.verification.FingerprintVerifier import net.schmizz.sshj.transport.verification.FingerprintVerifier
import net.schmizz.sshj.transport.verification.HostKeyVerifier 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.PasswordFinder
import net.schmizz.sshj.userauth.password.Resource import net.schmizz.sshj.userauth.password.Resource
import org.eclipse.jgit.transport.CredentialsProvider 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.transport.URIish
import org.eclipse.jgit.util.FS import org.eclipse.jgit.util.FS
sealed class SshAuthData { sealed class SshAuthMethod(val activity: FragmentActivity) {
class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData() class Password(activity: FragmentActivity) : SshAuthMethod(activity)
class SshKey(val passphraseFinder: InteractivePasswordFinder) : SshAuthData() class SshKey(activity: FragmentActivity) : SshAuthMethod(activity)
class OpenKeychain(val activity: FragmentActivity) : SshAuthData() class OpenKeychain(activity: FragmentActivity) : SshAuthMethod(activity)
} }
abstract class InteractivePasswordFinder : PasswordFinder { abstract class InteractivePasswordFinder : PasswordFinder {
@@ -62,12 +66,12 @@ abstract class InteractivePasswordFinder : PasswordFinder {
final override fun shouldRetry(resource: Resource<*>?) = true 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 private var currentSession: SshjSession? = null
override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession { 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" } d { "New SSH connection created" }
currentSession = it 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 lateinit var ssh: SSHClient
private var currentCommand: Session? = null 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) ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
if (!ssh.isConnected) if (!ssh.isConnected)
throw IOException() throw IOException()
when (authData) { val passwordAuth = AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password))
is SshAuthData.Password -> { when (authMethod) {
ssh.authPassword(username, authData.passwordFinder) is SshAuthMethod.Password -> {
ssh.auth(username, passwordAuth)
} }
is SshAuthData.SshKey -> { is SshAuthMethod.SshKey -> {
ssh.authPublickey(username, SshKey.provide(ssh, authData.passphraseFinder)) val pubkeyAuth = AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)))
ssh.auth(username, pubkeyAuth, passwordAuth)
} }
is SshAuthData.OpenKeychain -> { is SshAuthMethod.OpenKeychain -> {
runBlocking { runBlocking {
OpenKeychainKeyProvider.prepareAndUse(authData.activity) { provider -> OpenKeychainKeyProvider.prepareAndUse(authMethod.activity) { provider ->
ssh.authPublickey(username, provider) val openKeychainAuth = AuthPublickey(provider)
ssh.auth(username, openKeychainAuth, passwordAuth)
} }
} }
} }