diff options
| author | Fabian Henneke <FabianHenneke@users.noreply.github.com> | 2020-09-01 10:12:27 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-01 13:42:27 +0530 |
| commit | cbb96397d1f5cffef7984b08a426646aaea93e5b (patch) | |
| tree | da11f9d477758f69f7349dea56bfcee3a92b2446 /app/src/main/java/com/zeapo/pwdstore/git/operation | |
| parent | 55d64fb73710d8dcadd28089ab233cd543830184 (diff) | |
| download | HardenedPass-cbb96397d1f5cffef7984b08a426646aaea93e5b.tar.gz HardenedPass-cbb96397d1f5cffef7984b08a426646aaea93e5b.zip | |
Add Keystore backend for SSH public key authentication (#1070)
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/git/operation')
| -rw-r--r-- | app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt | 80 |
1 files changed, 60 insertions, 20 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt index 806bbb7c..1b47d79b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/operation/GitOperation.kt @@ -5,6 +5,7 @@ package com.zeapo.pwdstore.git.operation import android.content.Intent +import android.widget.Toast import androidx.annotation.CallSuper import androidx.core.content.edit import androidx.fragment.app.FragmentActivity @@ -17,12 +18,18 @@ import com.zeapo.pwdstore.git.config.AuthMode import com.zeapo.pwdstore.git.config.GitSettings import com.zeapo.pwdstore.git.sshj.InteractivePasswordFinder import com.zeapo.pwdstore.git.sshj.SshAuthData +import com.zeapo.pwdstore.git.sshj.SshKey import com.zeapo.pwdstore.git.sshj.SshjSessionFactory +import com.zeapo.pwdstore.utils.BiometricAuthenticator import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PreferenceKeys import com.zeapo.pwdstore.utils.getEncryptedPrefs import com.zeapo.pwdstore.utils.sharedPrefs import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import net.schmizz.sshj.userauth.password.PasswordFinder import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.GitCommand @@ -33,6 +40,8 @@ import org.eclipse.jgit.transport.CredentialsProvider import org.eclipse.jgit.transport.SshSessionFactory import org.eclipse.jgit.transport.URIish +const val ANDROID_KEYSTORE_ALIAS_SSH_KEY = "ssh_key" + /** * Creates a new git operation * @@ -43,7 +52,6 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment abstract val commands: Array<GitCommand<out Any>> private var provider: CredentialsProvider? = null - private val sshKeyFile = callingActivity.filesDir.resolve(".ssh_key") private val hostKeyFile = callingActivity.filesDir.resolve(".host_key") protected var finishFromErrorDialog = true protected val repository = PasswordRepository.getRepository(gitDir) @@ -61,9 +69,10 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment when (item) { is CredentialItem.Username -> item.value = uri?.user is CredentialItem.Password -> { - item.value = cachedPassword?.clone() ?: passwordFinder.reqPassword(null).also { - cachedPassword = it.clone() - } + item.value = cachedPassword?.clone() + ?: passwordFinder.reqPassword(null).also { + cachedPassword = it.clone() + } } else -> UnsupportedCredentialItem(uri, item.javaClass.name) } @@ -88,8 +97,8 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment return this } - private fun withPublicKeyAuthentication(passphraseFinder: InteractivePasswordFinder): GitOperation { - val sessionFactory = SshjSessionFactory(SshAuthData.PublicKeyFile(sshKeyFile, passphraseFinder), hostKeyFile) + private fun withSshKeyAuthentication(passphraseFinder: InteractivePasswordFinder): GitOperation { + val sessionFactory = SshjSessionFactory(SshAuthData.SshKey(passphraseFinder), hostKeyFile) SshSessionFactory.setInstance(sessionFactory) this.provider = null return this @@ -126,27 +135,58 @@ abstract class GitOperation(gitDir: File, internal val callingActivity: Fragment */ abstract suspend fun execute() + private fun onMissingSshKeyFile() { + MaterialAlertDialogBuilder(callingActivity) + .setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text)) + .setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title)) + .setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ -> + getSshKey(false) + } + .setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ -> + getSshKey(true) + } + .setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> + // Finish the blank GitActivity so user doesn't have to press back + callingActivity.finish() + }.show() + } + suspend fun executeAfterAuthentication( authMode: AuthMode, ) { when (authMode) { - AuthMode.SshKey -> if (!sshKeyFile.exists()) { - MaterialAlertDialogBuilder(callingActivity) - .setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text)) - .setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title)) - .setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ -> - getSshKey(false) + AuthMode.SshKey -> if (SshKey.exists) { + if (SshKey.mustAuthenticate) { + val result = withContext(Dispatchers.Main) { + suspendCoroutine<BiometricAuthenticator.Result> { cont -> + BiometricAuthenticator.authenticate(callingActivity, R.string.biometric_prompt_title_ssh_auth) { + if (it !is BiometricAuthenticator.Result.Failure) + cont.resume(it) + } + } } - .setNegativeButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_generate)) { _, _ -> - getSshKey(true) + when (result) { + is BiometricAuthenticator.Result.Success -> { + withSshKeyAuthentication(CredentialFinder(callingActivity, authMode)).execute() + } + is BiometricAuthenticator.Result.Cancelled -> callingActivity.finish() + is BiometricAuthenticator.Result.Failure -> { + throw IllegalStateException("Biometric authentication failures should be ignored") + } + else -> { + // There is a chance we succeed if the user recently confirmed + // their screen lock. Doing so would have a potential to confuse + // users though, who might deduce that the screen lock + // protection is not effective. Hence, we fail with an error. + Toast.makeText(callingActivity.applicationContext, R.string.biometric_auth_generic_failure, Toast.LENGTH_LONG).show() + callingActivity.finish() + } } - .setNeutralButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ -> - // Finish the blank GitActivity so user doesn't have to press back - callingActivity.finish() - }.show() + } else { + withSshKeyAuthentication(CredentialFinder(callingActivity, authMode)).execute() + } } else { - withPublicKeyAuthentication( - CredentialFinder(callingActivity, authMode)).execute() + onMissingSshKeyFile() } AuthMode.OpenKeychain -> withOpenKeychainAuthentication(callingActivity).execute() AuthMode.Password -> withPasswordAuthentication( |
