Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 9e0fb93

Browse files
Support multiple authentication methods (#825)
* Offer password SSH authentication after publickey * git: re-add back button handling Signed-off-by: Harsh Shandilya <[email protected]> * Hide unsupported authentication methods Signed-off-by: Harsh Shandilya <[email protected]> * GitCommandExecutor: cleanup and address build warning Signed-off-by: Harsh Shandilya <[email protected]> * Address review comments Signed-off-by: Harsh Shandilya <[email protected]> * DecryptActivity: hide menu items until decrypt finishes Signed-off-by: Harsh Shandilya <[email protected]> * Add changelog entry Signed-off-by: Harsh Shandilya <[email protected]> Co-authored-by: Harsh Shandilya <[email protected]>
1 parent ff780b0 commit 9e0fb93

File tree

4 files changed

+32
-28
lines changed

4 files changed

+32
-28
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
1010
- Add [Bromite](https://www.bromite.org/) and [Ungoogled Chromium](https://git.droidware.info/wchen342/ungoogled-chromium-android) to supported browsers list for Autofill
1111
- Add ability to view the Git commit log
1212
- Allow generating ECDSA and ED25519 keys for SSH
13+
- Add support for multiple/fallback authentication methods for SSH
1314

1415
### Changed
1516

app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import com.zeapo.pwdstore.UserPreference
1919
import com.zeapo.pwdstore.git.GitCommandExecutor
2020
import com.zeapo.pwdstore.git.config.AuthMode
2121
import com.zeapo.pwdstore.git.config.GitSettings
22-
import com.zeapo.pwdstore.git.sshj.SshAuthData
22+
import com.zeapo.pwdstore.git.sshj.SshAuthMethod
2323
import com.zeapo.pwdstore.git.sshj.SshKey
2424
import com.zeapo.pwdstore.git.sshj.SshjSessionFactory
2525
import com.zeapo.pwdstore.utils.BiometricAuthenticator
@@ -99,8 +99,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
9999
}
100100
}
101101

102-
private fun registerAuthProviders(authData: SshAuthData, credentialsProvider: CredentialsProvider? = null) {
103-
sshSessionFactory = SshjSessionFactory(authData, hostKeyFile)
102+
private fun registerAuthProviders(authMethod: SshAuthMethod, credentialsProvider: CredentialsProvider? = null) {
103+
sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile)
104104
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
105105
command.setTransportConfigCallback { transport: Transport ->
106106
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
@@ -154,8 +154,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
154154
}
155155
when (result) {
156156
is BiometricAuthenticator.Result.Success -> {
157-
registerAuthProviders(
158-
SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey)))
157+
registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
159158
}
160159
is BiometricAuthenticator.Result.Cancelled -> {
161160
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
@@ -173,21 +172,18 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
173172
}
174173
}
175174
} else {
176-
registerAuthProviders(SshAuthData.SshKey(CredentialFinder(callingActivity, AuthMode.SshKey)))
175+
registerAuthProviders(SshAuthMethod.SshKey(callingActivity))
177176
}
178177
} else {
179178
onMissingSshKeyFile()
180179
// This would correctly cancel the operation but won't surface a user-visible
181180
// error, allowing users to make the SSH key selection.
182181
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
183182
}
184-
AuthMode.OpenKeychain -> registerAuthProviders(SshAuthData.OpenKeychain(callingActivity))
183+
AuthMode.OpenKeychain -> registerAuthProviders(SshAuthMethod.OpenKeychain(callingActivity))
185184
AuthMode.Password -> {
186-
val credentialFinder = CredentialFinder(callingActivity, AuthMode.Password)
187-
val httpsCredentialProvider = HttpsCredentialsProvider(credentialFinder)
188-
registerAuthProviders(
189-
SshAuthData.Password(CredentialFinder(callingActivity, AuthMode.Password)),
190-
httpsCredentialProvider)
185+
val httpsCredentialProvider = HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password))
186+
registerAuthProviders(SshAuthMethod.Password(callingActivity), httpsCredentialProvider)
191187
}
192188
AuthMode.None -> {
193189
}

app/src/main/java/com/zeapo/pwdstore/git/sshj/OpenKeychainKeyProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import org.openintents.ssh.authentication.response.Response
3939
import org.openintents.ssh.authentication.response.SigningResponse
4040
import org.openintents.ssh.authentication.response.SshPublicKeyResponse
4141

42-
class OpenKeychainKeyProvider private constructor(private val activity: FragmentActivity) : KeyProvider, Closeable {
42+
class OpenKeychainKeyProvider private constructor(activity: FragmentActivity) : KeyProvider, Closeable {
4343

4444
companion object {
4545

app/src/main/java/com/zeapo/pwdstore/git/sshj/SshjSessionFactory.kt

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.github.ajalt.timberkt.d
1010
import com.github.ajalt.timberkt.w
1111
import com.github.michaelbull.result.getOrElse
1212
import com.github.michaelbull.result.runCatching
13+
import com.zeapo.pwdstore.git.config.AuthMode
14+
import com.zeapo.pwdstore.git.operation.CredentialFinder
1315
import java.io.File
1416
import java.io.IOException
1517
import java.io.InputStream
@@ -28,6 +30,8 @@ import net.schmizz.sshj.common.SecurityUtils
2830
import net.schmizz.sshj.connection.channel.direct.Session
2931
import net.schmizz.sshj.transport.verification.FingerprintVerifier
3032
import net.schmizz.sshj.transport.verification.HostKeyVerifier
33+
import net.schmizz.sshj.userauth.method.AuthPassword
34+
import net.schmizz.sshj.userauth.method.AuthPublickey
3135
import net.schmizz.sshj.userauth.password.PasswordFinder
3236
import net.schmizz.sshj.userauth.password.Resource
3337
import org.eclipse.jgit.transport.CredentialsProvider
@@ -36,10 +40,10 @@ import org.eclipse.jgit.transport.SshSessionFactory
3640
import org.eclipse.jgit.transport.URIish
3741
import org.eclipse.jgit.util.FS
3842

39-
sealed class SshAuthData {
40-
class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData()
41-
class SshKey(val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
42-
class OpenKeychain(val activity: FragmentActivity) : SshAuthData()
43+
sealed class SshAuthMethod(val activity: FragmentActivity) {
44+
class Password(activity: FragmentActivity) : SshAuthMethod(activity)
45+
class SshKey(activity: FragmentActivity) : SshAuthMethod(activity)
46+
class OpenKeychain(activity: FragmentActivity) : SshAuthMethod(activity)
4347
}
4448

4549
abstract class InteractivePasswordFinder : PasswordFinder {
@@ -62,12 +66,12 @@ abstract class InteractivePasswordFinder : PasswordFinder {
6266
final override fun shouldRetry(resource: Resource<*>?) = true
6367
}
6468

65-
class SshjSessionFactory(private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
69+
class SshjSessionFactory(private val authMethod: SshAuthMethod, private val hostKeyFile: File) : SshSessionFactory() {
6670

6771
private var currentSession: SshjSession? = null
6872

6973
override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession {
70-
return currentSession ?: SshjSession(uri, uri.user, authData, hostKeyFile).connect().also {
74+
return currentSession ?: SshjSession(uri, uri.user, authMethod, hostKeyFile).connect().also {
7175
d { "New SSH connection created" }
7276
currentSession = it
7377
}
@@ -100,7 +104,7 @@ private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier {
100104
}
101105
}
102106

103-
private class SshjSession(uri: URIish, private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : RemoteSession {
107+
private class SshjSession(uri: URIish, private val username: String, private val authMethod: SshAuthMethod, private val hostKeyFile: File) : RemoteSession {
104108

105109
private lateinit var ssh: SSHClient
106110
private var currentCommand: Session? = null
@@ -124,17 +128,20 @@ private class SshjSession(uri: URIish, private val username: String, private val
124128
ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
125129
if (!ssh.isConnected)
126130
throw IOException()
127-
when (authData) {
128-
is SshAuthData.Password -> {
129-
ssh.authPassword(username, authData.passwordFinder)
131+
val passwordAuth = AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password))
132+
when (authMethod) {
133+
is SshAuthMethod.Password -> {
134+
ssh.auth(username, passwordAuth)
130135
}
131-
is SshAuthData.SshKey -> {
132-
ssh.authPublickey(username, SshKey.provide(ssh, authData.passphraseFinder))
136+
is SshAuthMethod.SshKey -> {
137+
val pubkeyAuth = AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)))
138+
ssh.auth(username, pubkeyAuth, passwordAuth)
133139
}
134-
is SshAuthData.OpenKeychain -> {
140+
is SshAuthMethod.OpenKeychain -> {
135141
runBlocking {
136-
OpenKeychainKeyProvider.prepareAndUse(authData.activity) { provider ->
137-
ssh.authPublickey(username, provider)
142+
OpenKeychainKeyProvider.prepareAndUse(authMethod.activity) { provider ->
143+
val openKeychainAuth = AuthPublickey(provider)
144+
ssh.auth(username, openKeychainAuth, passwordAuth)
138145
}
139146
}
140147
}

0 commit comments

Comments
 (0)