aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle.kts1
-rw-r--r--app/src/main/java/app/passwordstore/injection/ssh/SSHKeyManagerModule.kt21
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt16
-rw-r--r--app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt8
-rw-r--r--app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt9
-rw-r--r--app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt39
-rw-r--r--app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt33
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt24
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt17
9 files changed, 113 insertions, 55 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5d005676..c6ca5871 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -50,6 +50,7 @@ dependencies {
implementation(projects.formatCommon)
implementation(projects.passgen.diceware)
implementation(projects.passgen.random)
+ implementation(projects.ssh)
implementation(projects.uiCompose)
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.activity.compose)
diff --git a/app/src/main/java/app/passwordstore/injection/ssh/SSHKeyManagerModule.kt b/app/src/main/java/app/passwordstore/injection/ssh/SSHKeyManagerModule.kt
new file mode 100644
index 00000000..c5abd487
--- /dev/null
+++ b/app/src/main/java/app/passwordstore/injection/ssh/SSHKeyManagerModule.kt
@@ -0,0 +1,21 @@
+package app.passwordstore.injection.ssh
+
+import android.content.Context
+import app.passwordstore.ssh.SSHKeyManager
+import dagger.Module
+import dagger.Provides
+import dagger.Reusable
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+object SSHKeyManagerModule {
+
+ @Provides
+ @Reusable
+ fun provideSSHKeyManager(@ApplicationContext context: Context): SSHKeyManager {
+ return SSHKeyManager(context)
+ }
+}
diff --git a/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt b/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
index 8bd61f41..c1ec8cbb 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
@@ -9,13 +9,14 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutManager
import android.os.Build
-import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.core.content.edit
import androidx.core.content.getSystemService
import androidx.fragment.app.FragmentActivity
import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.injection.prefs.GitPreferences
+import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.ui.git.config.GitConfigActivity
import app.passwordstore.ui.git.config.GitServerConfigActivity
import app.passwordstore.ui.proxy.ProxySelectorActivity
@@ -27,7 +28,6 @@ import app.passwordstore.util.extensions.launchActivity
import app.passwordstore.util.extensions.sharedPrefs
import app.passwordstore.util.extensions.snackbar
import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.git.sshj.SshKey
import app.passwordstore.util.settings.GitSettings
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.onFailure
@@ -43,11 +43,13 @@ import de.Maxr1998.modernpreferences.helpers.onClick
import de.Maxr1998.modernpreferences.helpers.pref
import de.Maxr1998.modernpreferences.helpers.switch
-class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider {
-
+class RepositorySettings(
+ private val activity: FragmentActivity,
+ private val sshKeyManager: SSHKeyManager,
+) : SettingsProvider {
private val generateSshKey =
- activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- showSshKeyPref?.visible = SshKey.canShowSshPublicKey
+ activity.registerForActivityResult(StartActivityForResult()) {
+ showSshKeyPref?.visible = sshKeyManager.canShowPublicKey()
}
private val hiltEntryPoint by unsafeLazy {
@@ -112,7 +114,7 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
showSshKeyPref =
pref(PreferenceKeys.SSH_SEE_KEY) {
titleRes = R.string.pref_ssh_see_key_title
- visible = PasswordRepository.isGitRepo() && SshKey.canShowSshPublicKey
+ visible = PasswordRepository.isGitRepo() && sshKeyManager.canShowPublicKey()
onClick {
ShowSshKeyFragment().show(activity.supportFragmentManager, "public_key")
true
diff --git a/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt b/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
index 30e2b1b0..697d0156 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
@@ -11,19 +11,24 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.BundleCompat
import app.passwordstore.R
import app.passwordstore.databinding.ActivityPreferenceRecyclerviewBinding
+import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.util.extensions.viewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
import de.Maxr1998.modernpreferences.Preference
import de.Maxr1998.modernpreferences.PreferencesAdapter
import de.Maxr1998.modernpreferences.helpers.screen
import de.Maxr1998.modernpreferences.helpers.subScreen
+import javax.inject.Inject
+@AndroidEntryPoint
class SettingsActivity : AppCompatActivity() {
+ @Inject lateinit var sshKeyManager: SSHKeyManager
+ private lateinit var repositorySettings: RepositorySettings
private val miscSettings = MiscSettings(this)
private val autofillSettings = AutofillSettings(this)
private val passwordSettings = PasswordSettings(this)
- private val repositorySettings = RepositorySettings(this)
private val generalSettings = GeneralSettings(this)
private val pgpSettings = PGPSettings(this)
@@ -35,6 +40,7 @@ class SettingsActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(binding.root)
Preference.Config.dialogBuilderFactory = { context -> MaterialAlertDialogBuilder(context) }
+ repositorySettings = RepositorySettings(this, sshKeyManager)
val screen =
screen(this) {
subScreen {
diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt
index a42d6aa1..4f52b540 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt
@@ -9,14 +9,19 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import app.passwordstore.R
-import app.passwordstore.util.git.sshj.SshKey
+import app.passwordstore.ssh.SSHKeyManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+@AndroidEntryPoint
class ShowSshKeyFragment : DialogFragment() {
+ @Inject lateinit var sshKeyManager: SSHKeyManager
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val activity = requireActivity()
- val publicKey = SshKey.sshPublicKey
+ val publicKey = sshKeyManager.publicKey()
return MaterialAlertDialogBuilder(requireActivity()).run {
setMessage(getString(R.string.ssh_keygen_message, publicKey))
setTitle(R.string.your_public_key)
diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt
index 67528d24..eea2d659 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt
@@ -17,11 +17,12 @@ import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
import app.passwordstore.databinding.ActivitySshKeygenBinding
import app.passwordstore.injection.prefs.GitPreferences
+import app.passwordstore.ssh.SSHKeyAlgorithm
+import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.util.auth.BiometricAuthenticator
import app.passwordstore.util.auth.BiometricAuthenticator.Result
import app.passwordstore.util.extensions.keyguardManager
import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.git.sshj.SshKey
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -33,24 +34,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-private enum class KeyGenType(val generateKey: suspend (requireAuthentication: Boolean) -> Unit) {
- Rsa({ requireAuthentication ->
- SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Rsa, requireAuthentication)
- }),
- Ecdsa({ requireAuthentication ->
- SshKey.generateKeystoreNativeKey(SshKey.Algorithm.Ecdsa, requireAuthentication)
- }),
- Ed25519({ requireAuthentication ->
- SshKey.generateKeystoreWrappedEd25519Key(requireAuthentication)
- }),
-}
-
@AndroidEntryPoint
class SshKeyGenActivity : AppCompatActivity() {
- private var keyGenType = KeyGenType.Ecdsa
+ private var sshKeyAlgorithm = SSHKeyAlgorithm.ECDSA
private val binding by viewBinding(ActivitySshKeygenBinding::inflate)
@GitPreferences @Inject lateinit var gitPrefs: SharedPreferences
+ @Inject lateinit var sshKeyManager: SSHKeyManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -58,7 +48,7 @@ class SshKeyGenActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
with(binding) {
generate.setOnClickListener {
- if (SshKey.exists) {
+ if (sshKeyManager.keyExists()) {
MaterialAlertDialogBuilder(this@SshKeyGenActivity).run {
setTitle(R.string.ssh_keygen_existing_title)
setMessage(R.string.ssh_keygen_existing_message)
@@ -79,18 +69,18 @@ class SshKeyGenActivity : AppCompatActivity() {
keyTypeExplanation.setText(R.string.ssh_keygen_explanation_ecdsa)
keyTypeGroup.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (isChecked) {
- keyGenType =
+ sshKeyAlgorithm =
when (checkedId) {
- R.id.key_type_ed25519 -> KeyGenType.Ed25519
- R.id.key_type_ecdsa -> KeyGenType.Ecdsa
- R.id.key_type_rsa -> KeyGenType.Rsa
+ R.id.key_type_ed25519 -> SSHKeyAlgorithm.ED25519
+ R.id.key_type_ecdsa -> SSHKeyAlgorithm.ECDSA
+ R.id.key_type_rsa -> SSHKeyAlgorithm.RSA
else -> throw IllegalStateException("Impossible key type selection")
}
keyTypeExplanation.setText(
- when (keyGenType) {
- KeyGenType.Ed25519 -> R.string.ssh_keygen_explanation_ed25519
- KeyGenType.Ecdsa -> R.string.ssh_keygen_explanation_ecdsa
- KeyGenType.Rsa -> R.string.ssh_keygen_explanation_rsa
+ when (sshKeyAlgorithm) {
+ SSHKeyAlgorithm.ED25519 -> R.string.ssh_keygen_explanation_ed25519
+ SSHKeyAlgorithm.ECDSA -> R.string.ssh_keygen_explanation_ecdsa
+ SSHKeyAlgorithm.RSA -> R.string.ssh_keygen_explanation_rsa
}
)
}
@@ -136,9 +126,10 @@ class SshKeyGenActivity : AppCompatActivity() {
if (result !is Result.Success)
throw UserNotAuthenticatedException(getString(R.string.biometric_auth_generic_failure))
}
- keyGenType.generateKey(requireAuthentication)
+ sshKeyManager.generateKey(sshKeyAlgorithm, requireAuthentication)
}
}
+ // Check if we still need this
gitPrefs.edit { remove("ssh_key_local_passphrase") }
binding.generate.apply {
text = getString(R.string.ssh_keygen_generate)
diff --git a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt
index 99b3bf3f..a5d276ae 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt
@@ -10,14 +10,21 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
-import app.passwordstore.util.git.sshj.SshKey
+import app.passwordstore.ssh.SSHKeyManager
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+@AndroidEntryPoint
class SshKeyImportActivity : AppCompatActivity() {
+ @Inject lateinit var sshKeyManager: SSHKeyManager
+
private val sshKeyImportAction =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
if (uri == null) {
@@ -25,15 +32,17 @@ class SshKeyImportActivity : AppCompatActivity() {
return@registerForActivityResult
}
runCatching {
- SshKey.import(uri)
- Toast.makeText(
- this,
- resources.getString(R.string.ssh_key_success_dialog_title),
- Toast.LENGTH_LONG
- )
- .show()
- setResult(RESULT_OK)
- finish()
+ lifecycleScope.launch {
+ sshKeyManager.importKey(uri)
+ Toast.makeText(
+ this@SshKeyImportActivity,
+ resources.getString(R.string.ssh_key_success_dialog_title),
+ Toast.LENGTH_LONG
+ )
+ .show()
+ setResult(RESULT_OK)
+ finish()
+ }
}
.onFailure { e ->
MaterialAlertDialogBuilder(this)
@@ -46,8 +55,8 @@ class SshKeyImportActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (SshKey.exists) {
- MaterialAlertDialogBuilder(this).run {
+ if (sshKeyManager.keyExists()) {
+ MaterialAlertDialogBuilder(this@SshKeyImportActivity).run {
setTitle(R.string.ssh_keygen_existing_title)
setMessage(R.string.ssh_keygen_existing_message)
setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ -> importSshKey() }
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt
index 92f17890..07ce7245 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt
@@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository
+import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.ui.sshkeygen.SshKeyGenActivity
import app.passwordstore.ui.sshkeygen.SshKeyImportActivity
import app.passwordstore.util.auth.BiometricAuthenticator
@@ -19,7 +20,6 @@ import app.passwordstore.util.auth.BiometricAuthenticator.Result.Retry
import app.passwordstore.util.auth.BiometricAuthenticator.Result.Success
import app.passwordstore.util.git.GitCommandExecutor
import app.passwordstore.util.git.sshj.SshAuthMethod
-import app.passwordstore.util.git.sshj.SshKey
import app.passwordstore.util.git.sshj.SshjSessionFactory
import app.passwordstore.util.settings.AuthMode
import com.github.michaelbull.result.Err
@@ -28,6 +28,10 @@ import com.github.michaelbull.result.Result
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.EntryPointAccessors
+import dagger.hilt.components.SingletonComponent
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
@@ -62,6 +66,12 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
open val requiresAuth: Boolean = true
private val hostKeyFile = callingActivity.filesDir.resolve(".host_key")
private var sshSessionFactory: SshjSessionFactory? = null
+ private val hiltEntryPoint =
+ EntryPointAccessors.fromApplication(
+ callingActivity.applicationContext,
+ GitOperationEntryPoint::class.java
+ )
+ private val sshKeyManager = hiltEntryPoint.sshKeyManager()
protected val repository = PasswordRepository.repository!!
protected val git = Git(repository)
private val authActivity
@@ -115,7 +125,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
authMethod: SshAuthMethod,
credentialsProvider: CredentialsProvider? = null
) {
- sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile)
+ sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile, sshKeyManager)
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
command.setTransportConfigCallback { transport: Transport ->
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
@@ -163,8 +173,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
suspend fun executeAfterAuthentication(authMode: AuthMode): Result<Unit, Throwable> {
when (authMode) {
AuthMode.SshKey ->
- if (SshKey.exists) {
- if (SshKey.mustAuthenticate) {
+ if (sshKeyManager.keyExists()) {
+ if (sshKeyManager.needsAuthentication()) {
val result =
withContext(Dispatchers.Main) {
suspendCoroutine<BiometricAuthenticator.Result> { cont ->
@@ -231,4 +241,10 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
/** Timeout in seconds before [TransportCommand] will abort a stalled IO operation. */
private const val CONNECT_TIMEOUT = 10
}
+
+ @EntryPoint
+ @InstallIn(SingletonComponent::class)
+ interface GitOperationEntryPoint {
+ fun sshKeyManager(): SSHKeyManager
+ }
}
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt b/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt
index 58af8495..c6e648c5 100644
--- a/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt
+++ b/app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt
@@ -6,6 +6,7 @@ package app.passwordstore.util.git.sshj
import android.util.Base64
import androidx.appcompat.app.AppCompatActivity
+import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.util.git.operation.CredentialFinder
import app.passwordstore.util.settings.AuthMode
import com.github.michaelbull.result.getOrElse
@@ -65,8 +66,11 @@ abstract class InteractivePasswordFinder : PasswordFinder {
final override fun shouldRetry(resource: Resource<*>?) = true
}
-class SshjSessionFactory(private val authMethod: SshAuthMethod, private val hostKeyFile: File) :
- SshSessionFactory() {
+class SshjSessionFactory(
+ private val authMethod: SshAuthMethod,
+ private val hostKeyFile: File,
+ private val sshKeyManager: SSHKeyManager,
+) : SshSessionFactory() {
private var currentSession: SshjSession? = null
@@ -77,7 +81,7 @@ class SshjSessionFactory(private val authMethod: SshAuthMethod, private val host
tms: Int
): RemoteSession {
return currentSession
- ?: SshjSession(uri, uri.user, authMethod, hostKeyFile).connect().also {
+ ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, sshKeyManager).connect().also {
logcat { "New SSH connection created" }
currentSession = it
}
@@ -120,7 +124,8 @@ private class SshjSession(
uri: URIish,
private val username: String,
private val authMethod: SshAuthMethod,
- private val hostKeyFile: File
+ private val hostKeyFile: File,
+ private val sshKeyManager: SSHKeyManager,
) : RemoteSession {
private lateinit var ssh: SSHClient
@@ -154,7 +159,9 @@ private class SshjSession(
}
is SshAuthMethod.SshKey -> {
val pubkeyAuth =
- AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)))
+ AuthPublickey(
+ sshKeyManager.keyProvider(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey))
+ )
ssh.auth(username, pubkeyAuth, passwordAuth)
}
}