summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2023-12-15 17:40:11 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2023-12-15 18:53:58 +0530
commitced2008a85250a20e409364da9f8e640347adb1f (patch)
treeff0b10762d7a72b34c151362898070002122a75b
parent4d5b32d98b0c97a790eb429844c79ef5d37d9e7a (diff)
Revert "Refactor SSHKey into a separate module (#2450)"
This reverts commit 97b3577a463966e93d24649ff348fc4bb6825e50.
-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.kt12
-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.kt17
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt10
-rw-r--r--settings.gradle.kts2
-rw-r--r--ssh/build.gradle.kts27
-rw-r--r--ssh/lint-baseline.xml25
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/SSHKey.kt5
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyAlgorithm.kt7
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyManager.kt267
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyType.kt15
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/generator/ECDSAKeyGenerator.kt53
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/generator/ED25519KeyGenerator.kt12
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/generator/RSAKeyGenerator.kt54
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/generator/SSHKeyGenerator.kt11
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreNativeKeyProvider.kt37
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreWrappedEd25519KeyProvider.kt55
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/utils/Constants.kt11
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/utils/Exceptions.kt12
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/utils/Extensions.kt49
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/utils/SSHKeyUtils.kt50
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/writer/ED25519KeyWriter.kt38
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/writer/ImportedKeyWriter.kt12
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/writer/KeystoreNativeKeyWriter.kt14
-rw-r--r--ssh/src/main/kotlin/app/passwordstore/ssh/writer/SSHKeyWriter.kt9
-rw-r--r--ssh/src/main/res/values/strings.xml5
31 files changed, 55 insertions, 865 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index abd26932..85144bbc 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -47,7 +47,6 @@ dependencies {
implementation(projects.format.common)
implementation(projects.passgen.diceware)
implementation(projects.passgen.random)
- implementation(projects.ssh)
implementation(projects.ui.compose)
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
deleted file mode 100644
index c5abd487..00000000
--- a/app/src/main/java/app/passwordstore/injection/ssh/SSHKeyManagerModule.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-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 462924ac..c6c30b88 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/RepositorySettings.kt
@@ -15,7 +15,6 @@ 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,6 +26,7 @@ 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
@@ -42,13 +42,11 @@ import de.Maxr1998.modernpreferences.helpers.onClick
import de.Maxr1998.modernpreferences.helpers.pref
import de.Maxr1998.modernpreferences.helpers.switch
-class RepositorySettings(
- private val activity: FragmentActivity,
- private val sshKeyManager: SSHKeyManager,
-) : SettingsProvider {
+class RepositorySettings(private val activity: FragmentActivity) : SettingsProvider {
+
private val generateSshKey =
activity.registerForActivityResult(StartActivityForResult()) {
- showSshKeyPref?.visible = sshKeyManager.canShowPublicKey()
+ showSshKeyPref?.visible = SshKey.canShowSshPublicKey
}
private val hiltEntryPoint by unsafeLazy {
@@ -113,7 +111,7 @@ class RepositorySettings(
showSshKeyPref =
pref(PreferenceKeys.SSH_SEE_KEY) {
titleRes = R.string.pref_ssh_see_key_title
- visible = PasswordRepository.isGitRepo() && sshKeyManager.canShowPublicKey()
+ visible = PasswordRepository.isGitRepo() && SshKey.canShowSshPublicKey
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 697d0156..30e2b1b0 100644
--- a/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/settings/SettingsActivity.kt
@@ -11,24 +11,19 @@ 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)
@@ -40,7 +35,6 @@ 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 4f52b540..a42d6aa1 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/ShowSshKeyFragment.kt
@@ -9,19 +9,14 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import app.passwordstore.R
-import app.passwordstore.ssh.SSHKeyManager
+import app.passwordstore.util.git.sshj.SshKey
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 = sshKeyManager.publicKey()
+ val publicKey = SshKey.sshPublicKey
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 34749e4c..68d5a40c 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyGenActivity.kt
@@ -17,13 +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.coroutines.DispatcherProvider
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
@@ -34,13 +33,24 @@ import kotlin.coroutines.suspendCoroutine
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 sshKeyAlgorithm = SSHKeyAlgorithm.ECDSA
+ private var keyGenType = KeyGenType.Ecdsa
private val binding by viewBinding(ActivitySshKeygenBinding::inflate)
@GitPreferences @Inject lateinit var gitPrefs: SharedPreferences
- @Inject lateinit var sshKeyManager: SSHKeyManager
@Inject lateinit var dispatcherProvider: DispatcherProvider
override fun onCreate(savedInstanceState: Bundle?) {
@@ -49,7 +59,7 @@ class SshKeyGenActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
with(binding) {
generate.setOnClickListener {
- if (sshKeyManager.keyExists()) {
+ if (SshKey.exists) {
MaterialAlertDialogBuilder(this@SshKeyGenActivity).run {
setTitle(R.string.ssh_keygen_existing_title)
setMessage(R.string.ssh_keygen_existing_message)
@@ -70,18 +80,18 @@ class SshKeyGenActivity : AppCompatActivity() {
keyTypeExplanation.setText(R.string.ssh_keygen_explanation_ecdsa)
keyTypeGroup.addOnButtonCheckedListener { _, checkedId, isChecked ->
if (isChecked) {
- sshKeyAlgorithm =
+ keyGenType =
when (checkedId) {
- R.id.key_type_ed25519 -> SSHKeyAlgorithm.ED25519
- R.id.key_type_ecdsa -> SSHKeyAlgorithm.ECDSA
- R.id.key_type_rsa -> SSHKeyAlgorithm.RSA
+ R.id.key_type_ed25519 -> KeyGenType.Ed25519
+ R.id.key_type_ecdsa -> KeyGenType.Ecdsa
+ R.id.key_type_rsa -> KeyGenType.Rsa
else -> throw IllegalStateException("Impossible key type selection")
}
keyTypeExplanation.setText(
- 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
+ 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
}
)
}
@@ -127,10 +137,9 @@ class SshKeyGenActivity : AppCompatActivity() {
if (result !is Result.Success)
throw UserNotAuthenticatedException(getString(R.string.biometric_auth_generic_failure))
}
- sshKeyManager.generateKey(sshKeyAlgorithm, requireAuthentication)
+ keyGenType.generateKey(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 a5d276ae..99b3bf3f 100644
--- a/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/sshkeygen/SshKeyImportActivity.kt
@@ -10,21 +10,14 @@ 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.ssh.SSHKeyManager
+import app.passwordstore.util.git.sshj.SshKey
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) {
@@ -32,17 +25,15 @@ class SshKeyImportActivity : AppCompatActivity() {
return@registerForActivityResult
}
runCatching {
- 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()
- }
+ SshKey.import(uri)
+ Toast.makeText(
+ this,
+ resources.getString(R.string.ssh_key_success_dialog_title),
+ Toast.LENGTH_LONG
+ )
+ .show()
+ setResult(RESULT_OK)
+ finish()
}
.onFailure { e ->
MaterialAlertDialogBuilder(this)
@@ -55,8 +46,8 @@ class SshKeyImportActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if (sshKeyManager.keyExists()) {
- MaterialAlertDialogBuilder(this@SshKeyImportActivity).run {
+ if (SshKey.exists) {
+ MaterialAlertDialogBuilder(this).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 199fde94..89194b79 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
@@ -9,7 +9,6 @@ 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
@@ -21,6 +20,7 @@ import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.launchActivity
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
@@ -67,11 +67,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
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()
+ EntryPointAccessors.fromApplication<GitOperationEntryPoint>(callingActivity)
protected val repository = PasswordRepository.repository!!
protected val git = Git(repository)
private val authActivity
@@ -121,7 +117,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
authMethod: SshAuthMethod,
credentialsProvider: CredentialsProvider? = null
) {
- sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile, sshKeyManager, hiltEntryPoint.dispatcherProvider())
+ sshSessionFactory =
+ SshjSessionFactory(authMethod, hostKeyFile, hiltEntryPoint.dispatcherProvider())
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
command.setTransportConfigCallback { transport: Transport ->
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
@@ -169,8 +166,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
suspend fun executeAfterAuthentication(authMode: AuthMode): Result<Unit, Throwable> {
when (authMode) {
AuthMode.SshKey ->
- if (sshKeyManager.keyExists()) {
- if (sshKeyManager.needsAuthentication()) {
+ if (SshKey.exists) {
+ if (SshKey.mustAuthenticate) {
val result =
withContext(hiltEntryPoint.dispatcherProvider().main()) {
suspendCoroutine { cont ->
@@ -247,8 +244,6 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
@EntryPoint
@InstallIn(SingletonComponent::class)
interface GitOperationEntryPoint {
- fun sshKeyManager(): SSHKeyManager
-
fun dispatcherProvider(): DispatcherProvider
}
}
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 86416cd6..5e11a636 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,7 +6,6 @@ package app.passwordstore.util.git.sshj
import android.util.Base64
import androidx.appcompat.app.AppCompatActivity
-import app.passwordstore.ssh.SSHKeyManager
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.git.operation.CredentialFinder
import app.passwordstore.util.settings.AuthMode
@@ -71,7 +70,6 @@ abstract class InteractivePasswordFinder(private val dispatcherProvider: Dispatc
class SshjSessionFactory(
private val authMethod: SshAuthMethod,
private val hostKeyFile: File,
- private val sshKeyManager: SSHKeyManager,
private val dispatcherProvider: DispatcherProvider,
) : SshSessionFactory() {
@@ -84,7 +82,7 @@ class SshjSessionFactory(
tms: Int
): RemoteSession {
return currentSession
- ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, dispatcherProvider, sshKeyManager).connect().also {
+ ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, dispatcherProvider).connect().also {
logcat { "New SSH connection created" }
currentSession = it
}
@@ -129,7 +127,6 @@ private class SshjSession(
private val authMethod: SshAuthMethod,
private val hostKeyFile: File,
private val dispatcherProvider: DispatcherProvider,
- private val sshKeyManager: SSHKeyManager,
) : RemoteSession {
private lateinit var ssh: SSHClient
@@ -165,7 +162,10 @@ private class SshjSession(
is SshAuthMethod.SshKey -> {
val pubkeyAuth =
AuthPublickey(
- sshKeyManager.keyProvider(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider))
+ SshKey.provide(
+ ssh,
+ CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider)
+ )
)
ssh.auth(username, pubkeyAuth, passwordAuth)
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2ceea6d6..1c24ddac 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -194,5 +194,3 @@ include("passgen:random")
include("sentry-stub")
include("ui:compose")
-
-include("ssh")
diff --git a/ssh/build.gradle.kts b/ssh/build.gradle.kts
deleted file mode 100644
index a975efab..00000000
--- a/ssh/build.gradle.kts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-@file:Suppress("UnstableApiUsage")
-
-plugins {
- id("com.github.android-password-store.android-library")
- id("com.github.android-password-store.kotlin-android")
-}
-
-android {
- namespace = "app.passwordstore.ssh"
- buildFeatures { androidResources = true }
- sourceSets { getByName("test") { resources.srcDir("src/main/res/raw") } }
-}
-
-dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.kotlinx.coroutines.android)
- implementation(libs.kotlinx.coroutines.core)
- implementation(libs.thirdparty.sshj)
- implementation(libs.thirdparty.logcat)
- implementation(libs.androidx.security)
- implementation(libs.thirdparty.eddsa)
- implementation(libs.thirdparty.kotlinResult)
-}
diff --git a/ssh/lint-baseline.xml b/ssh/lint-baseline.xml
deleted file mode 100644
index 3a04da73..00000000
--- a/ssh/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha14" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha14)" variant="all" version="8.3.0-alpha14">
-
- <issue
- id="InvalidPackage"
- message="Invalid package reference in library; not included in Android: `javax.naming.directory`. Referenced from `org.bouncycastle.cert.dane.fetcher.JndiDANEFetcherFactory`.">
- <location
- file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar"/>
- </issue>
-
- <issue
- id="InvalidPackage"
- message="Invalid package reference in library; not included in Android: `javax.naming`. Referenced from `org.bouncycastle.cert.dane.fetcher.JndiDANEFetcherFactory.1`.">
- <location
- file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar"/>
- </issue>
-
- <issue
- id="TrustAllX509TrustManager"
- message="`checkServerTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers">
- <location
- file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar"/>
- </issue>
-
-</issues>
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKey.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKey.kt
deleted file mode 100644
index a9b7dba2..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKey.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package app.passwordstore.ssh
-
-import java.io.File
-
-public data class SSHKey(val privateKey: File, val publicKey: File, val type: SSHKeyType)
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyAlgorithm.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyAlgorithm.kt
deleted file mode 100644
index 1849c04d..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyAlgorithm.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package app.passwordstore.ssh
-
-public enum class SSHKeyAlgorithm {
- RSA,
- ECDSA,
- ED25519,
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyManager.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyManager.kt
deleted file mode 100644
index e90eedee..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyManager.kt
+++ /dev/null
@@ -1,267 +0,0 @@
-package app.passwordstore.ssh
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.net.Uri
-import android.os.Build
-import android.provider.OpenableColumns
-import android.security.keystore.KeyInfo
-import androidx.core.content.edit
-import app.passwordstore.ssh.generator.ECDSAKeyGenerator
-import app.passwordstore.ssh.generator.ED25519KeyGenerator
-import app.passwordstore.ssh.generator.RSAKeyGenerator
-import app.passwordstore.ssh.provider.KeystoreNativeKeyProvider
-import app.passwordstore.ssh.provider.KeystoreWrappedEd25519KeyProvider
-import app.passwordstore.ssh.utils.Constants
-import app.passwordstore.ssh.utils.Constants.ANDROIDX_SECURITY_KEYSET_PREF_NAME
-import app.passwordstore.ssh.utils.Constants.KEYSTORE_ALIAS
-import app.passwordstore.ssh.utils.Constants.PROVIDER_ANDROID_KEY_STORE
-import app.passwordstore.ssh.utils.NullKeyException
-import app.passwordstore.ssh.utils.SSHKeyNotFoundException
-import app.passwordstore.ssh.utils.SSHKeyUtils
-import app.passwordstore.ssh.utils.getEncryptedGitPrefs
-import app.passwordstore.ssh.utils.sharedPrefs
-import app.passwordstore.ssh.writer.ED25519KeyWriter
-import app.passwordstore.ssh.writer.ImportedKeyWriter
-import app.passwordstore.ssh.writer.KeystoreNativeKeyWriter
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.mapBoth
-import com.github.michaelbull.result.runCatching
-import java.io.File
-import java.io.IOException
-import java.io.InputStream
-import java.security.KeyFactory
-import java.security.KeyPair
-import java.security.KeyStore
-import java.security.PrivateKey
-import javax.crypto.SecretKey
-import javax.crypto.SecretKeyFactory
-import logcat.asLog
-import logcat.logcat
-import net.schmizz.sshj.SSHClient
-import net.schmizz.sshj.userauth.keyprovider.KeyProvider
-import net.schmizz.sshj.userauth.password.PasswordFinder
-
-public class SSHKeyManager(private val applicationContext: Context) {
-
- private val androidKeystore: KeyStore by
- lazy(LazyThreadSafetyMode.NONE) {
- KeyStore.getInstance(PROVIDER_ANDROID_KEY_STORE).apply { load(null) }
- }
- private val isStrongBoxSupported by
- lazy(LazyThreadSafetyMode.NONE) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
- applicationContext.packageManager.hasSystemFeature(
- PackageManager.FEATURE_STRONGBOX_KEYSTORE
- )
- else false
- }
-
- // Let's make this suspend so that we can use datastore's non-blocking apis
- private fun keyType(): SSHKeyType {
- return SSHKeyType.fromValue(
- applicationContext.sharedPrefs.getString(Constants.GIT_REMOTE_KEY_TYPE, null)
- ) ?: throw NullKeyException()
- }
-
- public fun keyExists(): Boolean {
- return runCatching { keyType() }.mapBoth(success = { true }, failure = { false })
- }
-
- public fun canShowPublicKey(): Boolean =
- runCatching {
- keyType() in
- listOf(
- SSHKeyType.LegacyGenerated,
- SSHKeyType.KeystoreNative,
- SSHKeyType.KeystoreWrappedEd25519
- )
- }
- .getOrElse { false }
-
- public fun publicKey(): String? =
- runCatching { createNewSSHKey(keyType = keyType()).publicKey.readText() }
- .getOrElse {
- return null
- }
-
- public fun needsAuthentication(): Boolean {
- return runCatching {
- val keyType = keyType()
- if (keyType == SSHKeyType.KeystoreNative || keyType == SSHKeyType.KeystoreWrappedEd25519)
- return false
-
- when (val key = androidKeystore.getKey(KEYSTORE_ALIAS, null)) {
- is PrivateKey -> {
- val factory = KeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE)
- factory.getKeySpec(key, KeyInfo::class.java).isUserAuthenticationRequired
- }
- is SecretKey -> {
- val factory = SecretKeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE)
- (factory.getKeySpec(key, KeyInfo::class.java) as KeyInfo).isUserAuthenticationRequired
- }
- else -> throw SSHKeyNotFoundException()
- }
- }
- .getOrElse { error ->
- // It is fine to swallow the exception here since it will reappear when the key
- // is used for SSH authentication and can then be shown in the UI.
- logcat { error.asLog() }
- false
- }
- }
-
- public suspend fun importKey(uri: Uri) {
- // First check whether the content at uri is likely an SSH private key.
- val fileSize =
- applicationContext.contentResolver
- .query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
- ?.use { cursor ->
- // Cursor returns only a single row.
- cursor.moveToFirst()
- cursor.getInt(0)
- } ?: throw IOException(applicationContext.getString(R.string.ssh_key_does_not_exist))
- // We assume that an SSH key's ideal size is > 0 bytes && < 100 kilobytes.
- require(fileSize in 1 until SSH_KEY_MAX_FILE_SIZE) {
- applicationContext.getString(R.string.ssh_key_import_error_not_an_ssh_key_message)
- }
- val sshKeyInputStream =
- applicationContext.contentResolver.openInputStream(uri)
- ?: throw IOException(applicationContext.getString(R.string.ssh_key_does_not_exist))
-
- importKey(sshKeyInputStream)
- }
-
- private suspend fun importKey(sshKeyInputStream: InputStream) {
- val lines = sshKeyInputStream.bufferedReader().readLines()
- // The file must have more than 2 lines, and the first and last line must have private key
- // markers.
- check(SSHKeyUtils.isValid(lines)) {
- applicationContext.getString(R.string.ssh_key_import_error_not_an_ssh_key_message)
- }
- // At this point, we are reasonably confident that we have actually been provided a private
- // key and delete the old key.
- deleteKey()
- val sshKey = createNewSSHKey(keyType = SSHKeyType.Imported)
- saveImportedKey(lines.joinToString("\n"), sshKey)
- }
-
- public suspend fun generateKey(algorithm: SSHKeyAlgorithm, requiresAuthentication: Boolean) {
- deleteKey()
- val (sshKeyGenerator, sshKeyType) =
- when (algorithm) {
- SSHKeyAlgorithm.RSA -> Pair(RSAKeyGenerator(), SSHKeyType.KeystoreNative)
- SSHKeyAlgorithm.ECDSA ->
- Pair(ECDSAKeyGenerator(isStrongBoxSupported), SSHKeyType.KeystoreNative)
- SSHKeyAlgorithm.ED25519 -> Pair(ED25519KeyGenerator(), SSHKeyType.KeystoreWrappedEd25519)
- }
- val keyPair = sshKeyGenerator.generateKey(requiresAuthentication)
- val sshKeyFile = createNewSSHKey(keyType = sshKeyType)
- saveGeneratedKey(keyPair, sshKeyFile, requiresAuthentication)
- }
-
- private suspend fun saveGeneratedKey(
- keyPair: KeyPair,
- sshKey: SSHKey,
- requiresAuthentication: Boolean
- ) {
- val sshKeyWriter =
- when (sshKey.type) {
- SSHKeyType.Imported ->
- throw UnsupportedOperationException("KeyType imported is not supported with a KeyPair")
- SSHKeyType.KeystoreNative -> KeystoreNativeKeyWriter()
- SSHKeyType.KeystoreWrappedEd25519 ->
- ED25519KeyWriter(applicationContext, requiresAuthentication)
- SSHKeyType.LegacyGenerated ->
- error("saveGeneratedKey should not be called with a legacy generated key")
- }
-
- sshKeyWriter.writeKeyPair(keyPair, sshKey)
- setSSHKeyType(sshKey.type)
- }
-
- private suspend fun saveImportedKey(key: String, sshKey: SSHKey) {
- val sshKeyWriter =
- when (sshKey.type) {
- SSHKeyType.Imported -> ImportedKeyWriter(key)
- SSHKeyType.KeystoreNative ->
- throw UnsupportedOperationException(
- "KeyType KeystoreNative is not supported with a string key"
- )
- SSHKeyType.KeystoreWrappedEd25519 ->
- throw UnsupportedOperationException(
- "KeyType KeystoreWrappedEd25519 is not supported with a string key"
- )
- SSHKeyType.LegacyGenerated ->
- error("saveImportedKey should not be called with a legacy generated key")
- }
-
- sshKeyWriter.writeKeyPair(KeyPair(null, null), sshKey)
- setSSHKeyType(SSHKeyType.Imported)
- }
-
- private fun deleteKey() {
- androidKeystore.deleteEntry(KEYSTORE_ALIAS)
- // Remove Tink key set used by AndroidX's EncryptedFile.
- applicationContext
- .getSharedPreferences(ANDROIDX_SECURITY_KEYSET_PREF_NAME, Context.MODE_PRIVATE)
- .edit { clear() }
- // If there's no keyType(), we'll just use SSHKeyType.Imported, since they key is going to be
- // deleted, it does not really matter what the key type is.
- // The other way to handle this is to return if the keyType() throws an exception.
- val sshKey =
- runCatching { createNewSSHKey(keyType = keyType()) }
- .getOrElse { createNewSSHKey(keyType = SSHKeyType.Imported) }
- if (sshKey.privateKey.isFile) {
- sshKey.privateKey.delete()
- }
- if (sshKey.publicKey.isFile) {
- sshKey.publicKey.delete()
- }
-
- clearSSHKeyPreferences()
- }
-
- public fun keyProvider(client: SSHClient, passphraseFinder: PasswordFinder): KeyProvider? {
- val sshKeyFile =
- runCatching { createNewSSHKey(keyType = keyType()) }
- .getOrElse {
- return null
- }
-
- return when (sshKeyFile.type) {
- SSHKeyType.LegacyGenerated,
- SSHKeyType.Imported -> client.loadKeys(sshKeyFile.privateKey.absolutePath, passphraseFinder)
- SSHKeyType.KeystoreNative -> KeystoreNativeKeyProvider(androidKeystore)
- SSHKeyType.KeystoreWrappedEd25519 ->
- KeystoreWrappedEd25519KeyProvider(applicationContext, sshKeyFile)
- }
- }
-
- private fun setSSHKeyType(sshKeyType: SSHKeyType) {
- applicationContext.sharedPrefs.edit {
- putString(Constants.GIT_REMOTE_KEY_TYPE, sshKeyType.value)
- }
- }
-
- private fun clearSSHKeyPreferences() {
- applicationContext.getEncryptedGitPrefs().edit { remove(Constants.SSH_KEY_LOCAL_PASSPHRASE) }
- applicationContext.sharedPrefs.edit { remove(Constants.GIT_REMOTE_KEY_TYPE) }
- }
-
- private fun createNewSSHKey(
- keyType: SSHKeyType,
- privateKeyFileName: String = Constants.PRIVATE_SSH_KEY_FILE_NAME,
- publicKeyFileName: String = Constants.PUBLIC_SSH_KEY_FILE_NAME
- ): SSHKey {
- val privateKeyFile = File(applicationContext.filesDir, privateKeyFileName)
- val publicKeyFile = File(applicationContext.filesDir, publicKeyFileName)
-
- return SSHKey(privateKeyFile, publicKeyFile, keyType)
- }
-
- private companion object {
-
- private const val SSH_KEY_MAX_FILE_SIZE = 100_000
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyType.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyType.kt
deleted file mode 100644
index b9318297..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/SSHKeyType.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package app.passwordstore.ssh
-
-public enum class SSHKeyType(internal val value: String) {
- Imported("imported"),
- KeystoreNative("keystore_native"),
- KeystoreWrappedEd25519("keystore_wrapped_ed25519"),
- // Behaves like `Imported`, but allows to view the public key.
- LegacyGenerated("legacy_generated"),
- ;
-
- public companion object {
-
- public fun fromValue(type: String?): SSHKeyType? = entries.associateBy { it.value }[type]
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ECDSAKeyGenerator.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ECDSAKeyGenerator.kt
deleted file mode 100644
index b32d0933..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ECDSAKeyGenerator.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package app.passwordstore.ssh.generator
-
-import android.os.Build
-import android.security.keystore.KeyGenParameterSpec
-import android.security.keystore.KeyProperties
-import app.passwordstore.ssh.utils.Constants.KEYSTORE_ALIAS
-import app.passwordstore.ssh.utils.Constants.PROVIDER_ANDROID_KEY_STORE
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-public class ECDSAKeyGenerator(private val isStrongBoxSupported: Boolean) : SSHKeyGenerator {
-
- override suspend fun generateKey(requiresAuthentication: Boolean): KeyPair {
- val algorithm = KeyProperties.KEY_ALGORITHM_EC
-
- val parameterSpec =
- KeyGenParameterSpec.Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_SIGN).run {
- setKeySize(ECDSA_KEY_SIZE)
- setAlgorithmParameterSpec(java.security.spec.ECGenParameterSpec("secp256r1"))
- setDigests(KeyProperties.DIGEST_SHA256)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- setIsStrongBoxBacked(isStrongBoxSupported)
- }
- if (requiresAuthentication) {
- setUserAuthenticationRequired(true)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- setUserAuthenticationParameters(
- SSHKeyGenerator.USER_AUTHENTICATION_TIMEOUT,
- KeyProperties.AUTH_DEVICE_CREDENTIAL
- )
- } else {
- @Suppress("DEPRECATION")
- setUserAuthenticationValidityDurationSeconds(
- SSHKeyGenerator.USER_AUTHENTICATION_TIMEOUT
- )
- }
- }
- build()
- }
-
- val keyPair =
- KeyPairGenerator.getInstance(algorithm, PROVIDER_ANDROID_KEY_STORE).run {
- initialize(parameterSpec)
- generateKeyPair()
- }
-
- return keyPair
- }
-
- private companion object {
- private const val ECDSA_KEY_SIZE = 256
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ED25519KeyGenerator.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ED25519KeyGenerator.kt
deleted file mode 100644
index 418e9ad9..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/ED25519KeyGenerator.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.passwordstore.ssh.generator
-
-import java.security.KeyPair
-import net.i2p.crypto.eddsa.KeyPairGenerator
-
-public class ED25519KeyGenerator : SSHKeyGenerator {
-
- override suspend fun generateKey(requiresAuthentication: Boolean): KeyPair {
- // Generate the ed25519 key pair and encrypt the private key.
- return KeyPairGenerator().generateKeyPair()
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/RSAKeyGenerator.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/generator/RSAKeyGenerator.kt
deleted file mode 100644
index 54d9ae03..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/RSAKeyGenerator.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package app.passwordstore.ssh.generator
-
-import android.os.Build
-import android.security.keystore.KeyGenParameterSpec
-import android.security.keystore.KeyProperties
-import app.passwordstore.ssh.utils.Constants.KEYSTORE_ALIAS
-import app.passwordstore.ssh.utils.Constants.PROVIDER_ANDROID_KEY_STORE
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-
-public class RSAKeyGenerator : SSHKeyGenerator {
-
- override suspend fun generateKey(requiresAuthentication: Boolean): KeyPair {
- val algorithm = KeyProperties.KEY_ALGORITHM_RSA
- // Generate Keystore-backed private key.
- val parameterSpec =
- KeyGenParameterSpec.Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_SIGN).run {
- setKeySize(RSA_KEY_SIZE)
- setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
- setDigests(
- KeyProperties.DIGEST_SHA1,
- KeyProperties.DIGEST_SHA256,
- KeyProperties.DIGEST_SHA512,
- )
- if (requiresAuthentication) {
- setUserAuthenticationRequired(true)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- setUserAuthenticationParameters(
- SSHKeyGenerator.USER_AUTHENTICATION_TIMEOUT,
- KeyProperties.AUTH_DEVICE_CREDENTIAL
- )
- } else {
- @Suppress("DEPRECATION")
- setUserAuthenticationValidityDurationSeconds(
- SSHKeyGenerator.USER_AUTHENTICATION_TIMEOUT
- )
- }
- }
- build()
- }
-
- val keyPair =
- KeyPairGenerator.getInstance(algorithm, PROVIDER_ANDROID_KEY_STORE).run {
- initialize(parameterSpec)
- generateKeyPair()
- }
-
- return keyPair
- }
-
- private companion object {
- private const val RSA_KEY_SIZE = 3072
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/SSHKeyGenerator.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/generator/SSHKeyGenerator.kt
deleted file mode 100644
index 09a64481..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/generator/SSHKeyGenerator.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package app.passwordstore.ssh.generator
-
-import java.security.KeyPair
-
-public interface SSHKeyGenerator {
- public suspend fun generateKey(requiresAuthentication: Boolean): KeyPair
-
- public companion object {
- public const val USER_AUTHENTICATION_TIMEOUT: Int = 30
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreNativeKeyProvider.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreNativeKeyProvider.kt
deleted file mode 100644
index bc556e71..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreNativeKeyProvider.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package app.passwordstore.ssh.provider
-
-import app.passwordstore.ssh.utils.Constants.KEYSTORE_ALIAS
-import app.passwordstore.ssh.utils.sshPrivateKey
-import app.passwordstore.ssh.utils.sshPublicKey
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.runCatching
-import java.io.IOException
-import java.security.KeyStore
-import java.security.PrivateKey
-import java.security.PublicKey
-import logcat.asLog
-import logcat.logcat
-import net.schmizz.sshj.common.KeyType
-import net.schmizz.sshj.userauth.keyprovider.KeyProvider
-
-internal class KeystoreNativeKeyProvider(private val androidKeystore: KeyStore) : KeyProvider {
-
- override fun getPublic(): PublicKey =
- runCatching { androidKeystore.sshPublicKey!! }
- .getOrElse { error ->
- logcat { error.asLog() }
- throw IOException("Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore", error)
- }
-
- override fun getPrivate(): PrivateKey =
- runCatching { androidKeystore.sshPrivateKey!! }
- .getOrElse { error ->
- logcat { error.asLog() }
- throw IOException(
- "Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore",
- error
- )
- }
-
- override fun getType(): KeyType = KeyType.fromKey(public)
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreWrappedEd25519KeyProvider.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreWrappedEd25519KeyProvider.kt
deleted file mode 100644
index 31a57998..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/provider/KeystoreWrappedEd25519KeyProvider.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package app.passwordstore.ssh.provider
-
-import android.content.Context
-import app.passwordstore.ssh.SSHKey
-import app.passwordstore.ssh.utils.SSHKeyUtils.getOrCreateWrappedPrivateKeyFile
-import app.passwordstore.ssh.utils.parseStringPublicKey
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.runCatching
-import java.io.IOException
-import java.security.PrivateKey
-import java.security.PublicKey
-import kotlinx.coroutines.runBlocking
-import logcat.asLog
-import logcat.logcat
-import net.i2p.crypto.eddsa.EdDSAPrivateKey
-import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
-import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
-import net.schmizz.sshj.common.KeyType
-import net.schmizz.sshj.userauth.keyprovider.KeyProvider
-
-internal class KeystoreWrappedEd25519KeyProvider(
- private val context: Context,
- private val sshKeyFile: SSHKey
-) : KeyProvider {
-
- override fun getPublic(): PublicKey =
- runCatching { sshKeyFile.publicKey.readText().parseStringPublicKey()!! }
- .getOrElse { error ->
- logcat { error.asLog() }
- throw IOException("Failed to get the public key for wrapped ed25519 key", error)
- }
-
- override fun getPrivate(): PrivateKey =
- runCatching {
- // The current MasterKey API does not allow getting a reference to an existing
- // one
- // without specifying the KeySpec for a new one. However, the value for passed
- // here
- // for `requireAuthentication` is not used as the key already exists at this
- // point.
- val encryptedPrivateKeyFile = runBlocking {
- getOrCreateWrappedPrivateKeyFile(context, false, sshKeyFile.privateKey)
- }
- val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() }
- EdDSAPrivateKey(
- EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC)
- )
- }
- .getOrElse { error ->
- logcat { error.asLog() }
- throw IOException("Failed to unwrap wrapped ed25519 key", error)
- }
-
- override fun getType(): KeyType = KeyType.fromKey(public)
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Constants.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Constants.kt
deleted file mode 100644
index ccc33094..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Constants.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package app.passwordstore.ssh.utils
-
-internal object Constants {
- const val ANDROIDX_SECURITY_KEYSET_PREF_NAME = "androidx_sshkey_keyset_prefs"
- const val GIT_REMOTE_KEY_TYPE = "git_remote_key_type"
- const val KEYSTORE_ALIAS = "sshkey"
- const val PRIVATE_SSH_KEY_FILE_NAME = ".ssh_key"
- const val PROVIDER_ANDROID_KEY_STORE = "AndroidKeyStore"
- const val PUBLIC_SSH_KEY_FILE_NAME = ".ssh_key.pub"
- const val SSH_KEY_LOCAL_PASSPHRASE = "ssh_key_local_passphrase"
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Exceptions.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Exceptions.kt
deleted file mode 100644
index db921ab6..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Exceptions.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.passwordstore.ssh.utils
-
-public sealed class SSHException(message: String? = null, cause: Throwable? = null) :
- Exception(message, cause)
-
-public class NullKeyException(message: String? = "keyType was null", cause: Throwable? = null) :
- SSHException(message, cause)
-
-public class SSHKeyNotFoundException(
- message: String? = "SSH key does not exist in Keystore",
- cause: Throwable? = null
-) : SSHException(message, cause)
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Extensions.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Extensions.kt
deleted file mode 100644
index e1c337c1..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/Extensions.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package app.passwordstore.ssh.utils
-
-import android.content.Context
-import android.content.SharedPreferences
-import android.util.Base64
-import androidx.security.crypto.EncryptedSharedPreferences
-import androidx.security.crypto.MasterKey
-import app.passwordstore.ssh.utils.Constants.KEYSTORE_ALIAS
-import java.security.KeyStore
-import java.security.PrivateKey
-import java.security.PublicKey
-import net.schmizz.sshj.common.Buffer
-import net.schmizz.sshj.common.KeyType
-
-/** Get the default [SharedPreferences] instance */
-internal val Context.sharedPrefs: SharedPreferences
- get() = getSharedPreferences("app.passwordstore_preferences", 0)
-internal val KeyStore.sshPrivateKey
- get() = getKey(KEYSTORE_ALIAS, null) as? PrivateKey
-internal val KeyStore.sshPublicKey
- get() = getCertificate(KEYSTORE_ALIAS)?.publicKey
-
-internal fun String.parseStringPublicKey(): PublicKey? {
- val sshKeyParts = this.split("""\s+""".toRegex())
- if (sshKeyParts.size < 2) return null
- return Buffer.PlainBuffer(Base64.decode(sshKeyParts[1], Base64.NO_WRAP)).readPublicKey()
-}
-
-internal fun PublicKey.createStringPublicKey(): String {
- val rawPublicKey = Buffer.PlainBuffer().putPublicKey(this).compactData
- val keyType = KeyType.fromKey(this)
- return "$keyType ${Base64.encodeToString(rawPublicKey, Base64.NO_WRAP)}"
-}
-
-/** Wrapper for [getEncryptedPrefs] to avoid open-coding the file name at each call site */
-internal fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation")
-
-/** Get an instance of [EncryptedSharedPreferences] with the given [fileName] */
-private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences {
- val masterKeyAlias =
- MasterKey.Builder(applicationContext).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
- return EncryptedSharedPreferences.create(
- applicationContext,
- fileName,
- masterKeyAlias,
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
- )
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/SSHKeyUtils.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/utils/SSHKeyUtils.kt
deleted file mode 100644
index d192ea21..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/utils/SSHKeyUtils.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package app.passwordstore.ssh.utils
-
-import android.content.Context
-import androidx.security.crypto.EncryptedFile
-import androidx.security.crypto.MasterKey
-import java.io.File
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-internal object SSHKeyUtils {
-
- private const val USER_AUTHENTICATION_VALIDITY_DURATION = 15
-
- fun isValid(lines: List<String>): Boolean {
- return lines.size > 2 &&
- Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) &&
- Regex("END .* PRIVATE KEY").containsMatchIn(lines.last())
- }
-
- suspend fun getOrCreateWrappedPrivateKeyFile(
- context: Context,
- requiresAuthentication: Boolean,
- privateKeyFile: File
- ) =
- withContext(Dispatchers.IO) {
- EncryptedFile.Builder(
- context,
- privateKeyFile,
- getOrCreateWrappingMasterKey(context, requiresAuthentication),
- EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
- )
- .run {
- setKeysetPrefName(Constants.ANDROIDX_SECURITY_KEYSET_PREF_NAME)
- build()
- }
- }
-
- private suspend fun getOrCreateWrappingMasterKey(
- context: Context,
- requireAuthentication: Boolean
- ) =
- withContext(Dispatchers.IO) {
- MasterKey.Builder(context, Constants.KEYSTORE_ALIAS).run {
- setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
- setRequestStrongBoxBacked(true)
- setUserAuthenticationRequired(requireAuthentication, USER_AUTHENTICATION_VALIDITY_DURATION)
- build()
- }
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ED25519KeyWriter.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ED25519KeyWriter.kt
deleted file mode 100644
index ac2c983c..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ED25519KeyWriter.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package app.passwordstore.ssh.writer
-
-import android.content.Context
-import app.passwordstore.ssh.SSHKey
-import app.passwordstore.ssh.utils.SSHKeyUtils.getOrCreateWrappedPrivateKeyFile
-import app.passwordstore.ssh.utils.createStringPublicKey
-import java.io.File
-import java.security.KeyPair
-import java.security.PrivateKey
-import java.security.PublicKey
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import net.i2p.crypto.eddsa.EdDSAPrivateKey
-
-public class ED25519KeyWriter(
- private val context: Context,
- private val requiresAuthentication: Boolean,
-) : SSHKeyWriter {
-
- override suspend fun writeKeyPair(keyPair: KeyPair, sshKeyFile: SSHKey) {
- writePrivateKey(keyPair.private, sshKeyFile.privateKey)
- writePublicKey(keyPair.public, sshKeyFile.publicKey)
- }
-
- private suspend fun writePrivateKey(privateKey: PrivateKey, privateKeyFile: File) {
- withContext(Dispatchers.IO) {
- val encryptedPrivateKeyFile =
- getOrCreateWrappedPrivateKeyFile(context, requiresAuthentication, privateKeyFile)
- encryptedPrivateKeyFile.openFileOutput().use { os ->
- os.write((privateKey as EdDSAPrivateKey).seed)
- }
- }
- }
-
- private suspend fun writePublicKey(publicKey: PublicKey, publicKeyFile: File) {
- publicKeyFile.writeText(publicKey.createStringPublicKey())
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ImportedKeyWriter.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ImportedKeyWriter.kt
deleted file mode 100644
index 809a3d60..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/ImportedKeyWriter.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package app.passwordstore.ssh.writer
-
-import app.passwordstore.ssh.SSHKey
-import java.security.KeyPair
-
-public class ImportedKeyWriter(private val privateKey: String) : SSHKeyWriter {
-
- override suspend fun writeKeyPair(keyPair: KeyPair, sshKeyFile: SSHKey) {
- // Write the string key instead of the key from the key pair
- sshKeyFile.privateKey.writeText(privateKey)
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/KeystoreNativeKeyWriter.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/writer/KeystoreNativeKeyWriter.kt
deleted file mode 100644
index 24cc02b9..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/KeystoreNativeKeyWriter.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package app.passwordstore.ssh.writer
-
-import app.passwordstore.ssh.SSHKey
-import app.passwordstore.ssh.utils.createStringPublicKey
-import java.security.KeyPair
-
-public class KeystoreNativeKeyWriter : SSHKeyWriter {
-
- override suspend fun writeKeyPair(keyPair: KeyPair, sshKeyFile: SSHKey) {
- // Android Keystore manages the private key for us
- // Write public key in SSH format to .ssh_key.pub.
- sshKeyFile.publicKey.writeText(keyPair.public.createStringPublicKey())
- }
-}
diff --git a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/SSHKeyWriter.kt b/ssh/src/main/kotlin/app/passwordstore/ssh/writer/SSHKeyWriter.kt
deleted file mode 100644
index c5866086..00000000
--- a/ssh/src/main/kotlin/app/passwordstore/ssh/writer/SSHKeyWriter.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package app.passwordstore.ssh.writer
-
-import app.passwordstore.ssh.SSHKey
-import java.security.KeyPair
-
-public interface SSHKeyWriter {
-
- public suspend fun writeKeyPair(keyPair: KeyPair, sshKeyFile: SSHKey)
-}
diff --git a/ssh/src/main/res/values/strings.xml b/ssh/src/main/res/values/strings.xml
deleted file mode 100644
index f35fe0ac..00000000
--- a/ssh/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string name="ssh_key_does_not_exist">Unable to open the ssh private key, please check that the file exists</string>
- <string name="ssh_key_import_error_not_an_ssh_key_message">Selected file does not appear to be an SSH private key.</string>
-</resources>