summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/AndroidManifest.xml20
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt269
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt7
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt151
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt227
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/GetKeyIdsActivity.kt78
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt617
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivityV2.kt1
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt6
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt55
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/OtpImportDialogFragment.kt6
-rw-r--r--app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt6
-rw-r--r--app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt3
-rw-r--r--app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt4
-rw-r--r--app/src/main/java/app/passwordstore/ui/main/LaunchActivity.kt2
-rw-r--r--app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt48
-rw-r--r--app/src/main/java/app/passwordstore/util/crypto/GpgIdentifier.kt42
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt5
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/CloneOperation.kt4
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/GcOperation.kt4
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/GitOperation.kt5
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/PullOperation.kt4
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/PushOperation.kt5
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt5
-rw-r--r--app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt4
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/ContinuationContainerActivity.kt34
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainKeyProvider.kt225
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt103
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt19
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt16
-rw-r--r--app/src/main/java/app/passwordstore/util/settings/GitSettings.kt3
-rw-r--r--app/src/main/res/layout/activity_git_clone.xml6
-rw-r--r--app/src/main/res/layout/decrypt_layout.xml2
-rw-r--r--app/src/main/res/layout/folder_dialog_fragment.xml2
-rw-r--r--app/src/main/res/layout/password_creation_activity.xml2
-rw-r--r--app/src/test/java/app/passwordstore/util/crypto/GpgIdentifierTest.kt41
36 files changed, 60 insertions, 1971 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1d78e734..0e79441d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -96,27 +96,11 @@
android:parentActivityName=".ui.passwords.PasswordStore" />
<activity
- android:name=".ui.crypto.PasswordCreationActivity"
- android:exported="false"
- android:label="@string/new_password_title"
- android:windowSoftInputMode="adjustResize" />
-
- <activity
android:name=".ui.crypto.PasswordCreationActivityV2"
android:exported="false"
android:label="@string/new_password_title"
android:windowSoftInputMode="adjustResize" />
- <activity
- android:name=".ui.crypto.DecryptActivity"
- android:exported="false"
- android:windowSoftInputMode="adjustResize" />
-
- <activity
- android:name=".ui.crypto.GetKeyIdsActivity"
- android:exported="false"
- android:theme="@style/NoBackgroundThemeM3" />
-
<service
android:name=".util.services.ClipboardService"
android:exported="false"
@@ -151,10 +135,6 @@
android:label="@string/pref_ssh_keygen_title"
android:windowSoftInputMode="adjustResize" />
<activity
- android:name=".ui.autofill.AutofillDecryptActivity"
- android:exported="false"
- android:theme="@style/NoBackgroundThemeM3" />
- <activity
android:name=".ui.autofill.AutofillDecryptActivityV2"
android:exported="false"
android:theme="@style/NoBackgroundThemeM3" />
diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
deleted file mode 100644
index 1017d3a9..00000000
--- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.ui.autofill
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-import android.os.Build
-import android.os.Bundle
-import android.view.autofill.AutofillManager
-import android.widget.Toast
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
-import androidx.annotation.RequiresApi
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.data.passfile.PasswordEntry
-import app.passwordstore.util.autofill.AutofillPreferences
-import app.passwordstore.util.autofill.AutofillResponseBuilder
-import app.passwordstore.util.autofill.DirectoryStructure
-import app.passwordstore.util.extensions.OPENPGP_PROVIDER
-import app.passwordstore.util.extensions.asLog
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.onSuccess
-import com.github.michaelbull.result.runCatching
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.InputStream
-import java.io.OutputStream
-import javax.inject.Inject
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-import org.openintents.openpgp.OpenPgpError
-
-@RequiresApi(26)
-@AndroidEntryPoint
-class AutofillDecryptActivity : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_FILE_PATH = "app.passwordstore.autofill.oreo.EXTRA_FILE_PATH"
- private const val EXTRA_SEARCH_ACTION = "app.passwordstore.autofill.oreo.EXTRA_SEARCH_ACTION"
-
- private var decryptFileRequestCode = 1
-
- fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
- return Intent(context, AutofillDecryptActivity::class.java).apply {
- putExtras(forwardedExtras)
- putExtra(EXTRA_SEARCH_ACTION, true)
- putExtra(EXTRA_FILE_PATH, file.absolutePath)
- }
- }
-
- fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
- val intent =
- Intent(context, AutofillDecryptActivity::class.java).apply {
- putExtra(EXTRA_SEARCH_ACTION, false)
- putExtra(EXTRA_FILE_PATH, file.absolutePath)
- }
- return PendingIntent.getActivity(
- context,
- decryptFileRequestCode++,
- intent,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_CANCEL_CURRENT
- },
- )
- .intentSender
- }
- }
-
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
-
- private val decryptInteractionRequiredAction =
- registerForActivityResult(StartIntentSenderForResult()) { result ->
- if (continueAfterUserInteraction != null) {
- val data = result.data
- if (result.resultCode == RESULT_OK && data != null) {
- continueAfterUserInteraction?.resume(data)
- } else {
- continueAfterUserInteraction?.resumeWithException(
- Exception("OpenPgpApi ACTION_DECRYPT_VERIFY failed to continue after user interaction")
- )
- }
- continueAfterUserInteraction = null
- }
- }
-
- private var continueAfterUserInteraction: Continuation<Intent>? = null
- private lateinit var directoryStructure: DirectoryStructure
-
- override fun onStart() {
- super.onStart()
- val filePath =
- intent?.getStringExtra(EXTRA_FILE_PATH)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
- finish()
- return
- }
- val clientState =
- intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
- finish()
- return
- }
- val isSearchAction = intent?.getBooleanExtra(EXTRA_SEARCH_ACTION, true)!!
- val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
- directoryStructure = AutofillPreferences.directoryStructure(this)
- logcat { action.toString() }
- lifecycleScope.launch {
- val credentials = decryptCredential(File(filePath))
- if (credentials == null) {
- setResult(RESULT_CANCELED)
- } else {
- val fillInDataset =
- AutofillResponseBuilder.makeFillInDataset(
- this@AutofillDecryptActivity,
- credentials,
- clientState,
- action
- )
- withContext(Dispatchers.Main) {
- setResult(
- RESULT_OK,
- Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
- )
- }
- }
- withContext(Dispatchers.Main) { finish() }
- }
- }
-
- private suspend fun executeOpenPgpApi(
- data: Intent,
- input: InputStream,
- output: OutputStream
- ): Intent {
- var openPgpServiceConnection: OpenPgpServiceConnection? = null
- val openPgpService =
- suspendCoroutine<IOpenPgpService2> { cont ->
- openPgpServiceConnection =
- OpenPgpServiceConnection(
- this,
- OPENPGP_PROVIDER,
- object : OpenPgpServiceConnection.OnBound {
- override fun onBound(service: IOpenPgpService2) {
- cont.resume(service)
- }
-
- override fun onError(e: Exception) {
- cont.resumeWithException(e)
- }
- }
- )
- .also { it.bindToService() }
- }
- return OpenPgpApi(this, openPgpService).executeApi(data, input, output).also {
- openPgpServiceConnection?.unbindFromService()
- }
- }
-
- private suspend fun decryptCredential(file: File, resumeIntent: Intent? = null): Credentials? {
- val command = resumeIntent ?: Intent().apply { action = OpenPgpApi.ACTION_DECRYPT_VERIFY }
- runCatching { file.inputStream() }
- .onFailure { e ->
- logcat(ERROR) { e.asLog("File to decrypt not found") }
- return null
- }
- .onSuccess { encryptedInput ->
- val decryptedOutput = ByteArrayOutputStream()
- runCatching { executeOpenPgpApi(command, encryptedInput, decryptedOutput) }
- .onFailure { e ->
- logcat(ERROR) { e.asLog("OpenPgpApi ACTION_DECRYPT_VERIFY failed") }
- return null
- }
- .onSuccess { result ->
- return when (
- val resultCode =
- result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
- ) {
- OpenPgpApi.RESULT_CODE_SUCCESS -> {
- runCatching {
- val entry =
- withContext(Dispatchers.IO) {
- @Suppress("BlockingMethodInNonBlockingContext")
- passwordEntryFactory.create(decryptedOutput.toByteArray())
- }
- AutofillPreferences.credentialsFromStoreEntry(
- this,
- file,
- entry,
- directoryStructure
- )
- }
- .getOrElse { e ->
- logcat(ERROR) { e.asLog("Failed to parse password entry") }
- return null
- }
- }
- OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val pendingIntent: PendingIntent =
- result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!!
- runCatching {
- val intentToResume =
- withContext(Dispatchers.Main) {
- suspendCoroutine<Intent> { cont ->
- continueAfterUserInteraction = cont
- decryptInteractionRequiredAction.launch(
- IntentSenderRequest.Builder(pendingIntent.intentSender).build()
- )
- }
- }
- decryptCredential(file, intentToResume)
- }
- .getOrElse { e ->
- logcat(ERROR) {
- e.asLog("OpenPgpApi ACTION_DECRYPT_VERIFY failed with user interaction")
- }
- return null
- }
- }
- OpenPgpApi.RESULT_CODE_ERROR -> {
- val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
- if (error != null) {
- withContext(Dispatchers.Main) {
- Toast.makeText(
- applicationContext,
- "Error from OpenKeyChain: ${error.message}",
- Toast.LENGTH_LONG
- )
- .show()
- }
- logcat(ERROR) {
- "OpenPgpApi ACTION_DECRYPT_VERIFY failed (${error.errorId}): ${error.message}"
- }
- }
- null
- }
- else -> {
- logcat(ERROR) { "Unrecognized OpenPgpApi result: $resultCode" }
- null
- }
- }
- }
- }
- return null
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
index a5a0d34e..65ec4229 100644
--- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
@@ -15,7 +15,6 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.crypto.PasswordCreationActivity
import app.passwordstore.ui.crypto.PasswordCreationActivityV2
import app.passwordstore.util.autofill.AutofillMatcher
import app.passwordstore.util.autofill.AutofillPreferences
@@ -114,9 +113,9 @@ class AutofillSaveActivity : AppCompatActivity() {
bundleOf(
"REPO_PATH" to repo.absolutePath,
"FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)!!).absolutePath,
- PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
- PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
- PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to
+ PasswordCreationActivityV2.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
+ PasswordCreationActivityV2.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
+ PasswordCreationActivityV2.EXTRA_GENERATE_PASSWORD to
intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
)
)
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
index 7298e038..b6bb481f 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/BasePgpActivity.kt
@@ -5,12 +5,9 @@
package app.passwordstore.ui.crypto
-import android.app.PendingIntent
import android.content.ClipData
import android.content.Intent
-import android.content.IntentSender
import android.content.SharedPreferences
-import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
@@ -19,33 +16,20 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.R
import app.passwordstore.injection.prefs.SettingsPreferences
-import app.passwordstore.util.extensions.OPENPGP_PROVIDER
-import app.passwordstore.util.extensions.asLog
import app.passwordstore.util.extensions.clipboard
import app.passwordstore.util.extensions.getString
import app.passwordstore.util.extensions.snackbar
import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.features.Features
import app.passwordstore.util.services.ClipboardService
import app.passwordstore.util.settings.PreferenceKeys
-import com.github.michaelbull.result.getOr
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import javax.inject.Inject
-import logcat.LogPriority.ERROR
-import logcat.LogPriority.INFO
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-import org.openintents.openpgp.OpenPgpError
@Suppress("Registered")
@AndroidEntryPoint
-open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
+open class BasePgpActivity : AppCompatActivity() {
/** Full path to the repository */
val repoPath by unsafeLazy { intent.getStringExtra("REPO_PATH")!! }
@@ -63,20 +47,6 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
/** [SharedPreferences] instance used by subclasses to persist settings */
@SettingsPreferences @Inject lateinit var settings: SharedPreferences
- @Inject lateinit var features: Features
-
- /**
- * Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain.
- */
- private var serviceConnection: OpenPgpServiceConnection? = null
- var api: OpenPgpApi? = null
-
- /**
- * A [OpenPgpServiceConnection.OnBound] instance for the last listener that we wish to bind with
- * in case the previous attempt was cancelled due to missing [OPENPGP_PROVIDER] package.
- */
- private var previousListener: OpenPgpServiceConnection.OnBound? = null
-
/**
* [onCreate] sets the window up with the right flags to prevent auth leaks through screenshots or
* recent apps screen.
@@ -88,124 +58,6 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
}
/**
- * [onDestroy] handles unbinding from the OpenPgp service linked with [serviceConnection]. This is
- * annotated with [CallSuper] because it's critical to unbind the service to ensure we're not
- * leaking things.
- */
- @CallSuper
- override fun onDestroy() {
- super.onDestroy()
- serviceConnection?.unbindFromService()
- previousListener = null
- }
-
- /**
- * [onResume] controls the flow for resumption of a PGP operation that was previously interrupted
- * by the [OPENPGP_PROVIDER] package being missing.
- */
- override fun onResume() {
- super.onResume()
- previousListener?.let { bindToOpenKeychain(it) }
- }
-
- /**
- * Sets up [api] once the service is bound. Downstream consumers must call super this to
- * initialize [api]
- */
- @CallSuper
- override fun onBound(service: IOpenPgpService2) {
- api = OpenPgpApi(this, service)
- }
-
- /**
- * Mandatory error handling from [OpenPgpServiceConnection.OnBound]. All subclasses must handle
- * their own errors, and hence this class simply logs and rethrows. Subclasses Must NOT call
- * super.
- */
- override fun onError(e: Exception) {
- logcat(ERROR) { e.asLog("Callers must handle their own exceptions") }
- throw e
- }
-
- /** Method for subclasses to initiate binding with [OpenPgpServiceConnection]. */
- fun bindToOpenKeychain(onBoundListener: OpenPgpServiceConnection.OnBound) {
- if (true) return
- val installed =
- runCatching {
- packageManager.getPackageInfo(OPENPGP_PROVIDER, 0)
- true
- }
- .getOr(false)
- if (!installed) {
- previousListener = onBoundListener
- MaterialAlertDialogBuilder(this)
- .setTitle(getString(R.string.openkeychain_not_installed_title))
- .setMessage(getString(R.string.openkeychain_not_installed_message))
- .setPositiveButton(getString(R.string.openkeychain_not_installed_google_play)) { _, _ ->
- runCatching {
- val intent =
- Intent(Intent.ACTION_VIEW).apply {
- data = Uri.parse(getString(R.string.play_deeplink_template, OPENPGP_PROVIDER))
- setPackage("com.android.vending")
- }
- startActivity(intent)
- }
- }
- .setNeutralButton(getString(R.string.openkeychain_not_installed_fdroid)) { _, _ ->
- runCatching {
- val intent =
- Intent(Intent.ACTION_VIEW).apply {
- data = Uri.parse(getString(R.string.fdroid_deeplink_template, OPENPGP_PROVIDER))
- }
- startActivity(intent)
- }
- }
- .setOnCancelListener { finish() }
- .show()
- return
- } else {
- previousListener = null
- serviceConnection =
- OpenPgpServiceConnection(this, OPENPGP_PROVIDER, onBoundListener).also {
- it.bindToService()
- }
- }
- }
-
- /**
- * Handle the case where OpenKeychain returns that it needs to interact with the user
- *
- * @param result The intent returned by OpenKeychain
- */
- fun getUserInteractionRequestIntent(result: Intent): IntentSender {
- logcat(INFO) { "RESULT_CODE_USER_INTERACTION_REQUIRED" }
- return result.getParcelableExtra<PendingIntent>(OpenPgpApi.RESULT_INTENT)!!.intentSender
- }
-
- /**
- * Base handling of OpenKeychain errors based on the error contained in [result]. Subclasses can
- * use this when they want to default to sane error handling.
- */
- fun handleError(result: Intent) {
- val error: OpenPgpError? = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR)
- if (error != null) {
- when (error.errorId) {
- OpenPgpError.NO_OR_WRONG_PASSPHRASE -> {
- snackbar(message = getString(R.string.openpgp_error_wrong_passphrase))
- }
- OpenPgpError.NO_USER_IDS -> {
- snackbar(message = getString(R.string.openpgp_error_no_user_ids))
- }
- else -> {
- snackbar(message = getString(R.string.openpgp_error_unknown, error.message))
- logcat(ERROR) { "onError getErrorId: ${error.errorId}" }
- logcat(ERROR) { "onError getMessage: ${error.message}" }
- }
- }
- }
- }
-
- /**
* Copies provided [text] to the clipboard. Shows a [Snackbar] which can be disabled by passing
* [showSnackbar] as false.
*/
@@ -251,7 +103,6 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou
companion object {
- private const val TAG = "APS/BasePgpActivity"
const val EXTRA_FILE_PATH = "FILE_PATH"
const val EXTRA_REPO_PATH = "REPO_PATH"
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
deleted file mode 100644
index f3fac1f2..00000000
--- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.crypto
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.R
-import app.passwordstore.data.passfile.PasswordEntry
-import app.passwordstore.data.password.FieldItem
-import app.passwordstore.databinding.DecryptLayoutBinding
-import app.passwordstore.ui.adapters.FieldItemAdapter
-import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.settings.PreferenceKeys
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.ByteArrayOutputStream
-import java.io.File
-import javax.inject.Inject
-import kotlin.time.Duration
-import kotlin.time.ExperimentalTime
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.asLog
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-
-@AndroidEntryPoint
-class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
-
- private val binding by viewBinding(DecryptLayoutBinding::inflate)
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
-
- private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
- private var passwordEntry: PasswordEntry? = null
-
- private val userInteractionRequiredResult =
- registerForActivityResult(StartIntentSenderForResult()) { result ->
- if (result.data == null) {
- setResult(RESULT_CANCELED, null)
- finish()
- return@registerForActivityResult
- }
-
- when (result.resultCode) {
- RESULT_OK -> decryptAndVerify(result.data)
- RESULT_CANCELED -> {
- setResult(RESULT_CANCELED, result.data)
- finish()
- }
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- bindToOpenKeychain(this)
- title = name
- with(binding) {
- setContentView(root)
- passwordCategory.text = relativeParentPath
- passwordFile.text = name
- passwordFile.setOnLongClickListener {
- copyTextToClipboard(name)
- true
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.pgp_handler, menu)
- passwordEntry?.let { entry ->
- menu.findItem(R.id.edit_password).isVisible = true
- if (!entry.password.isNullOrBlank()) {
- menu.findItem(R.id.share_password_as_plaintext).isVisible = true
- menu.findItem(R.id.copy_password).isVisible = true
- }
- }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> onBackPressed()
- R.id.edit_password -> editPassword()
- R.id.share_password_as_plaintext -> shareAsPlaintext()
- R.id.copy_password -> copyPasswordToClipboard(passwordEntry?.password)
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- override fun onBound(service: IOpenPgpService2) {
- super.onBound(service)
- decryptAndVerify()
- }
-
- override fun onError(e: Exception) {
- logcat(ERROR) { e.asLog() }
- }
-
- /**
- * Automatically finishes the activity 60 seconds after decryption succeeded to prevent
- * information leaks from stale activities.
- */
- @OptIn(ExperimentalTime::class)
- private fun startAutoDismissTimer() {
- lifecycleScope.launch {
- delay(Duration.seconds(60))
- finish()
- }
- }
-
- /**
- * Edit the current password and hide all the fields populated by encrypted data so that when the
- * result triggers they can be repopulated with new data.
- */
- private fun editPassword() {
- val intent = Intent(this, PasswordCreationActivity::class.java)
- intent.putExtra("FILE_PATH", relativeParentPath)
- intent.putExtra("REPO_PATH", repoPath)
- intent.putExtra(PasswordCreationActivity.EXTRA_FILE_NAME, name)
- intent.putExtra(PasswordCreationActivity.EXTRA_PASSWORD, passwordEntry?.password)
- intent.putExtra(PasswordCreationActivity.EXTRA_EXTRA_CONTENT, passwordEntry?.extraContentString)
- intent.putExtra(PasswordCreationActivity.EXTRA_EDITING, true)
- startActivity(intent)
- finish()
- }
-
- private fun shareAsPlaintext() {
- val sendIntent =
- Intent().apply {
- action = Intent.ACTION_SEND
- putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
- type = "text/plain"
- }
- // Always show a picker to give the user a chance to cancel
- startActivity(
- Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to))
- )
- }
-
- @OptIn(ExperimentalTime::class)
- private fun decryptAndVerify(receivedIntent: Intent? = null) {
- if (api == null) {
- bindToOpenKeychain(this)
- return
- }
- val data = receivedIntent ?: Intent()
- data.action = OpenPgpApi.ACTION_DECRYPT_VERIFY
-
- val inputStream = File(fullPath).inputStream()
- val outputStream = ByteArrayOutputStream()
-
- lifecycleScope.launch(Dispatchers.Main) {
- val result =
- withContext(Dispatchers.IO) {
- checkNotNull(api).executeApi(data, inputStream, outputStream)
- }
- when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- OpenPgpApi.RESULT_CODE_SUCCESS -> {
- startAutoDismissTimer()
- runCatching {
- val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true)
- val entry = passwordEntryFactory.create(outputStream.toByteArray())
-
- if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) {
- copyPasswordToClipboard(entry.password)
- }
-
- passwordEntry = entry
- invalidateOptionsMenu()
-
- val items = arrayListOf<FieldItem>()
- if (!entry.password.isNullOrBlank()) {
- items.add(FieldItem.createPasswordField(entry.password!!))
- }
-
- if (entry.hasTotp()) {
- items.add(FieldItem.createOtpField(entry.totp.first()))
- }
-
- if (!entry.username.isNullOrBlank()) {
- items.add(FieldItem.createUsernameField(entry.username!!))
- }
-
- entry.extraContent.forEach { (key, value) ->
- items.add(FieldItem(key, value, FieldItem.ActionType.COPY))
- }
-
- val adapter =
- FieldItemAdapter(items, showPassword) { text -> copyTextToClipboard(text) }
- binding.recyclerView.adapter = adapter
- binding.recyclerView.itemAnimator = null
-
- if (entry.hasTotp()) {
- entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope)
- }
- }
- .onFailure { e -> logcat(ERROR) { e.asLog() } }
- }
- OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val sender = getUserInteractionRequestIntent(result)
- userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
- }
- OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/GetKeyIdsActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/GetKeyIdsActivity.kt
deleted file mode 100644
index 2b25db0b..00000000
--- a/app/src/main/java/app/passwordstore/ui/crypto/GetKeyIdsActivity.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.crypto
-
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
-import androidx.lifecycle.lifecycleScope
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.asLog
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpUtils
-import org.openintents.openpgp.IOpenPgpService2
-
-class GetKeyIdsActivity : BasePgpActivity() {
-
- private val userInteractionRequiredResult =
- registerForActivityResult(StartIntentSenderForResult()) { result ->
- if (result.data == null || result.resultCode == RESULT_CANCELED) {
- setResult(RESULT_CANCELED, result.data)
- finish()
- return@registerForActivityResult
- }
- getKeyIds(result.data!!)
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- bindToOpenKeychain(this)
- }
-
- override fun onBound(service: IOpenPgpService2) {
- super.onBound(service)
- getKeyIds()
- }
-
- override fun onError(e: Exception) {
- logcat(ERROR) { e.asLog() }
- }
-
- /** Get the Key ids from OpenKeychain */
- private fun getKeyIds(data: Intent = Intent()) {
- data.action = OpenPgpApi.ACTION_GET_KEY_IDS
- lifecycleScope.launch(Dispatchers.Main) {
- val result = withContext(Dispatchers.IO) { checkNotNull(api).executeApi(data, null, null) }
- when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- OpenPgpApi.RESULT_CODE_SUCCESS -> {
- runCatching {
- val ids =
- result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map {
- OpenPgpUtils.convertKeyIdToHex(it)
- }
- ?: emptyList()
- val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray())
- setResult(RESULT_OK, keyResult)
- finish()
- }
- .onFailure { e -> logcat(ERROR) { e.asLog() } }
- }
- OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val sender = getUserInteractionRequestIntent(result)
- userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
- }
- OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
deleted file mode 100644
index f432227e..00000000
--- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.ui.crypto
-
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.graphics.Bitmap
-import android.graphics.ImageDecoder
-import android.os.Build
-import android.os.Bundle
-import android.provider.MediaStore
-import android.text.InputType
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
-import androidx.core.content.edit
-import androidx.core.view.isVisible
-import androidx.core.widget.doAfterTextChanged
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.R
-import app.passwordstore.data.passfile.PasswordEntry
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.databinding.PasswordCreationActivityBinding
-import app.passwordstore.ui.dialogs.DicewarePasswordGeneratorDialogFragment
-import app.passwordstore.ui.dialogs.OtpImportDialogFragment
-import app.passwordstore.ui.dialogs.PasswordGeneratorDialogFragment
-import app.passwordstore.util.autofill.AutofillPreferences
-import app.passwordstore.util.autofill.DirectoryStructure
-import app.passwordstore.util.crypto.GpgIdentifier
-import app.passwordstore.util.extensions.asLog
-import app.passwordstore.util.extensions.base64
-import app.passwordstore.util.extensions.commitChange
-import app.passwordstore.util.extensions.getString
-import app.passwordstore.util.extensions.isInsideRepository
-import app.passwordstore.util.extensions.snackbar
-import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.settings.PreferenceKeys
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.onSuccess
-import com.github.michaelbull.result.runCatching
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
-import com.google.zxing.BinaryBitmap
-import com.google.zxing.LuminanceSource
-import com.google.zxing.RGBLuminanceSource
-import com.google.zxing.common.HybridBinarizer
-import com.google.zxing.integration.android.IntentIntegrator
-import com.google.zxing.integration.android.IntentIntegrator.QR_CODE
-import com.google.zxing.qrcode.QRCodeReader
-import dagger.hilt.android.AndroidEntryPoint
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.IOException
-import javax.inject.Inject
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.asLog
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-
-@AndroidEntryPoint
-class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
-
- private val binding by viewBinding(PasswordCreationActivityBinding::inflate)
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
-
- private val suggestedName by unsafeLazy { intent.getStringExtra(EXTRA_FILE_NAME) }
- private val suggestedPass by unsafeLazy { intent.getStringExtra(EXTRA_PASSWORD) }
- private val suggestedExtra by unsafeLazy { intent.getStringExtra(EXTRA_EXTRA_CONTENT) }
- private val shouldGeneratePassword by unsafeLazy {
- intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
- }
- private val editing by unsafeLazy { intent.getBooleanExtra(EXTRA_EDITING, false) }
- private val oldFileName by unsafeLazy { intent.getStringExtra(EXTRA_FILE_NAME) }
- private var oldCategory: String? = null
- private var copy: Boolean = false
- private var encryptionIntent: Intent = Intent()
-
- private val userInteractionRequiredResult =
- registerForActivityResult(StartIntentSenderForResult()) { result ->
- if (result.data == null) {
- setResult(RESULT_CANCELED, null)
- finish()
- return@registerForActivityResult
- }
-
- when (result.resultCode) {
- RESULT_OK -> encrypt(result.data)
- RESULT_CANCELED -> {
- setResult(RESULT_CANCELED, result.data)
- finish()
- }
- }
- }
-
- private val otpImportAction =
- registerForActivityResult(StartActivityForResult()) { result ->
- if (result.resultCode == RESULT_OK) {
- binding.otpImportButton.isVisible = false
- val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data)
- val contents = "${intentResult.contents}\n"
- val currentExtras = binding.extraContent.text.toString()
- if (currentExtras.isNotEmpty() && currentExtras.last() != '\n')
- binding.extraContent.append("\n$contents")
- else binding.extraContent.append(contents)
- snackbar(message = getString(R.string.otp_import_success))
- } else {
- snackbar(message = getString(R.string.otp_import_failure))
- }
- }
-
- private val imageImportAction =
- registerForActivityResult(ActivityResultContracts.GetContent()) { imageUri ->
- if (imageUri == null) {
- snackbar(message = getString(R.string.otp_import_failure))
- return@registerForActivityResult
- }
- val bitmap =
- if (Build.VERSION.SDK_INT >= 28) {
- ImageDecoder.decodeBitmap(ImageDecoder.createSource(contentResolver, imageUri))
- .copy(Bitmap.Config.ARGB_8888, true)
- } else {
- @Suppress("DEPRECATION") MediaStore.Images.Media.getBitmap(contentResolver, imageUri)
- }
- val intArray = IntArray(bitmap.width * bitmap.height)
- // copy pixel data from the Bitmap into the 'intArray' array
- bitmap.getPixels(intArray, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
- val source: LuminanceSource = RGBLuminanceSource(bitmap.width, bitmap.height, intArray)
- val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
-
- val reader = QRCodeReader()
- runCatching {
- val result = reader.decode(binaryBitmap)
- val text = result.text
- val currentExtras = binding.extraContent.text.toString()
- if (currentExtras.isNotEmpty() && currentExtras.last() != '\n')
- binding.extraContent.append("\n$text")
- else binding.extraContent.append(text)
- snackbar(message = getString(R.string.otp_import_success))
- binding.otpImportButton.isVisible = false
- }
- .onFailure { snackbar(message = getString(R.string.otp_import_failure)) }
- }
-
- private val gpgKeySelectAction =
- registerForActivityResult(StartActivityForResult()) { result ->
- if (result.resultCode == RESULT_OK) {
- result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
- lifecycleScope.launch {
- val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id")
- withContext(Dispatchers.IO) {
- gpgIdentifierFile.writeText((keyIds + "").joinToString("\n"))
- }
- commitChange(
- getString(
- R.string.git_commit_gpg_id,
- getLongName(
- gpgIdentifierFile.parentFile!!.absolutePath,
- repoPath,
- gpgIdentifierFile.name
- )
- )
- )
- .onSuccess { encrypt(encryptionIntent) }
- }
- }
- } else {
- snackbar(
- message = getString(R.string.gpg_key_select_mandatory),
- length = Snackbar.LENGTH_LONG
- )
- }
- }
-
- private fun File.findTillRoot(fileName: String, rootPath: File): File? {
- val gpgFile = File(this, fileName)
- if (gpgFile.exists()) return gpgFile
-
- if (this.absolutePath == rootPath.absolutePath) {
- return null
- }
-
- val parent = parentFile
- return if (parent != null && parent.exists()) {
- parent.findTillRoot(fileName, rootPath)
- } else {
- null
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- bindToOpenKeychain(this)
- title =
- if (editing) getString(R.string.edit_password) else getString(R.string.new_password_title)
- with(binding) {
- setContentView(root)
- generatePassword.setOnClickListener { generatePassword() }
- otpImportButton.setOnClickListener {
- supportFragmentManager.setFragmentResultListener(
- OTP_RESULT_REQUEST_KEY,
- this@PasswordCreationActivity
- ) { requestKey, bundle ->
- if (requestKey == OTP_RESULT_REQUEST_KEY) {
- val contents = bundle.getString(RESULT)
- val currentExtras = binding.extraContent.text.toString()
- if (currentExtras.isNotEmpty() && currentExtras.last() != '\n')
- binding.extraContent.append("\n$contents")
- else binding.extraContent.append(contents)
- }
- }
- val hasCamera = packageManager?.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) == true
- if (hasCamera) {
- val items =
- arrayOf(
- getString(R.string.otp_import_qr_code),
- getString(R.string.otp_import_from_file),
- getString(R.string.otp_import_manual_entry),
- )
- MaterialAlertDialogBuilder(this@PasswordCreationActivity)
- .setItems(items) { _, index ->
- when (index) {
- 0 ->
- otpImportAction.launch(
- IntentIntegrator(this@PasswordCreationActivity)
- .setOrientationLocked(false)
- .setBeepEnabled(false)
- .setDesiredBarcodeFormats(QR_CODE)
- .createScanIntent()
- )
- 1 -> imageImportAction.launch("image/*")
- 2 -> OtpImportDialogFragment().show(supportFragmentManager, "OtpImport")
- }
- }
- .show()
- } else {
- OtpImportDialogFragment().show(supportFragmentManager, "OtpImport")
- }
- }
-
- directoryInputLayout.apply {
- if (suggestedName != null || suggestedPass != null || shouldGeneratePassword) {
- isEnabled = true
- } else {
- setBackgroundColor(getColor(android.R.color.transparent))
- }
- val path = getRelativePath(fullPath, repoPath)
- // Keep empty path field visible if it is editable.
- if (path.isEmpty() && !isEnabled) visibility = View.GONE
- else {
- directory.setText(path)
- oldCategory = path
- }
- }
- if (suggestedName != null) {
- filename.setText(suggestedName)
- } else {
- filename.requestFocus()
- }
- // Allow the user to quickly switch between storing the username as the filename or
- // in the encrypted extras. This only makes sense if the directory structure is
- // FileBased.
- if (
- suggestedName == null &&
- AutofillPreferences.directoryStructure(this@PasswordCreationActivity) ==
- DirectoryStructure.FileBased
- ) {
- encryptUsername.apply {
- visibility = View.VISIBLE
- setOnClickListener {
- if (isChecked) {
- // User wants to enable username encryption, so we add it to the
- // encrypted extras as the first line.
- val username = filename.text.toString()
- val extras = "username:$username\n${extraContent.text}"
-
- filename.text?.clear()
- extraContent.setText(extras)
- } else {
- // User wants to disable username encryption, so we extract the
- // username from the encrypted extras and use it as the filename.
- val entry =
- passwordEntryFactory.create("PASSWORD\n${extraContent.text}".encodeToByteArray())
- val username = entry.username
-
- // username should not be null here by the logic in
- // updateViewState, but it could still happen due to
- // input lag.
- if (username != null) {
- filename.setText(username)
- extraContent.setText(entry.extraContentWithoutAuthData)
- }
- }
- }
- }
- }
- suggestedPass?.let {
- password.setText(it)
- password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- }
- suggestedExtra?.let { extraContent.setText(it) }
- if (shouldGeneratePassword) {
- generatePassword()
- password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- }
- }
- listOf(binding.filename, binding.extraContent).forEach {
- it.doAfterTextChanged { updateViewState() }
- }
- updateViewState()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.pgp_handler_new_password, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> {
- setResult(RESULT_CANCELED)
- onBackPressed()
- }
- R.id.save_password -> {
- copy = false
- encrypt()
- }
- R.id.save_and_copy_password -> {
- copy = true
- encrypt()
- }
- else -> return super.onOptionsItemSelected(item)
- }
- return true
- }
-
- private fun generatePassword() {
- supportFragmentManager.setFragmentResultListener(PASSWORD_RESULT_REQUEST_KEY, this) {
- requestKey,
- bundle ->
- if (requestKey == PASSWORD_RESULT_REQUEST_KEY) {
- binding.password.setText(bundle.getString(RESULT))
- }
- }
- when (settings.getString(PreferenceKeys.PREF_KEY_PWGEN_TYPE) ?: KEY_PWGEN_TYPE_CLASSIC) {
- KEY_PWGEN_TYPE_CLASSIC ->
- PasswordGeneratorDialogFragment().show(supportFragmentManager, "generator")
- KEY_PWGEN_TYPE_DICEWARE ->
- DicewarePasswordGeneratorDialogFragment().show(supportFragmentManager, "generator")
- }
- }
-
- private fun updateViewState() =
- with(binding) {
- // Use PasswordEntry to parse extras for username
- val entry =
- passwordEntryFactory.create("PLACEHOLDER\n${extraContent.text}".encodeToByteArray())
- encryptUsername.apply {
- if (visibility != View.VISIBLE) return@apply
- val hasUsernameInFileName = filename.text.toString().isNotBlank()
- val hasUsernameInExtras = !entry.username.isNullOrBlank()
- isEnabled = hasUsernameInFileName xor hasUsernameInExtras
- isChecked = hasUsernameInExtras
- }
- otpImportButton.isVisible = !entry.hasTotp()
- }
-
- /** Encrypts the password and the extra content */
- private fun encrypt(receivedIntent: Intent? = null) {
- with(binding) {
- val editName = filename.text.toString().trim()
- val editPass = password.text.toString()
- val editExtra = extraContent.text.toString()
-
- if (editName.isEmpty()) {
- snackbar(message = resources.getString(R.string.file_toast_text))
- return@with
- } else if (editName.contains('/')) {
- snackbar(message = resources.getString(R.string.invalid_filename_text))
- return@with
- }
-
- if (editPass.isEmpty() && editExtra.isEmpty()) {
- snackbar(message = resources.getString(R.string.empty_toast_text))
- return@with
- }
-
- if (copy) {
- copyPasswordToClipboard(editPass)
- }
-
- encryptionIntent = receivedIntent ?: Intent()
- encryptionIntent.action = OpenPgpApi.ACTION_ENCRYPT
-
- // pass enters the key ID into `.gpg-id`.
- val repoRoot = PasswordRepository.getRepositoryDirectory()
- val gpgIdentifierFile =
- File(repoRoot, directory.text.toString()).findTillRoot(".gpg-id", repoRoot)
- ?: File(repoRoot, ".gpg-id").apply { createNewFile() }
- val gpgIdentifiers =
- gpgIdentifierFile
- .readLines()
- .filter { it.isNotBlank() }
- .map { line ->
- GpgIdentifier.fromString(line)
- ?: run {
- // The line being empty means this is most likely an empty `.gpg-id`
- // file we created. Skip the validation so we can make the user add a
- // real ID.
- if (line.isEmpty()) return@run
- if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
- snackbar(message = resources.getString(R.string.short_key_ids_unsupported))
- } else {
- snackbar(message = resources.getString(R.string.invalid_gpg_id))
- }
- return@with
- }
- }
- if (gpgIdentifiers.isEmpty()) {
- gpgKeySelectAction.launch(
- Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java)
- )
- return@with
- }
- val keyIds =
- gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray()
- if (keyIds.isNotEmpty()) {
- encryptionIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds)
- }
- val userIds =
- gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray()
- if (userIds.isNotEmpty()) {
- encryptionIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, userIds)
- }
-
- encryptionIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, false)
-
- val content = "$editPass\n$editExtra"
- val inputStream = ByteArrayInputStream(content.toByteArray())
- val outputStream = ByteArrayOutputStream()
-
- val path =
- when {
- // If we allowed the user to edit the relative path, we have to consider it here
- // instead
- // of fullPath.
- directoryInputLayout.isEnabled -> {
- val editRelativePath = directory.text.toString().trim()
- if (editRelativePath.isEmpty()) {
- snackbar(message = resources.getString(R.string.path_toast_text))
- return
- }
- val passwordDirectory = File("$repoPath/${editRelativePath.trim('/')}")
- if (!passwordDirectory.exists() && !passwordDirectory.mkdir()) {
- snackbar(message = "Failed to create directory ${editRelativePath.trim('/')}")
- return
- }
-
- "${passwordDirectory.path}/$editName.gpg"
- }
- else -> "$fullPath/$editName.gpg"
- }
-
- lifecycleScope.launch(Dispatchers.Main) {
- val result =
- withContext(Dispatchers.IO) {
- checkNotNull(api).executeApi(encryptionIntent, inputStream, outputStream)
- }
- when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- OpenPgpApi.RESULT_CODE_SUCCESS -> {
- runCatching {
- val file = File(path)
- // If we're not editing, this file should not already exist!
- // Additionally, if we were editing and the incoming and outgoing
- // filenames differ, it means we renamed. Ensure that the target
- // doesn't already exist to prevent an accidental overwrite.
- if (
- (!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
- file.exists()
- ) {
- snackbar(message = getString(R.string.password_creation_duplicate_error))
- return@runCatching
- }
-
- if (!file.isInsideRepository()) {
- snackbar(message = getString(R.string.message_error_destination_outside_repo))
- return@runCatching
- }
-
- withContext(Dispatchers.IO) {
- file.outputStream().use { it.write(outputStream.toByteArray()) }
- }
-
- // associate the new password name with the last name's timestamp in
- // history
- val preference =
- getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
- val oldFilePathHash =
- "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64()
- val timestamp = preference.getString(oldFilePathHash)
- if (timestamp != null) {
- preference.edit {
- remove(oldFilePathHash)
- putString(file.absolutePath.base64(), timestamp)
- }
- }
-
- val returnIntent = Intent()
- returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path)
- returnIntent.putExtra(RETURN_EXTRA_NAME, editName)
- returnIntent.putExtra(
- RETURN_EXTRA_LONG_NAME,
- getLongName(fullPath, repoPath, editName)
- )
-
- if (shouldGeneratePassword) {
- val directoryStructure =
- AutofillPreferences.directoryStructure(applicationContext)
- val entry = passwordEntryFactory.create(content.encodeToByteArray())
- returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
- val username = entry.username ?: directoryStructure.getUsernameFor(file)
- returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
- }
-
- if (
- directoryInputLayout.isVisible &&
- directoryInputLayout.isEnabled &&
- oldFileName != null
- ) {
- val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg")
- if (oldFile.path != file.path && !oldFile.delete()) {
- setResult(RESULT_CANCELED)
- MaterialAlertDialogBuilder(this@PasswordCreationActivity)
- .setTitle(R.string.password_creation_file_fail_title)
- .setMessage(
- getString(R.string.password_creation_file_delete_fail_message, oldFileName)
- )
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok) { _, _ -> finish() }
- .show()
- return@runCatching
- }
- }
-
- val commitMessageRes =
- if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text
- lifecycleScope.launch {
- commitChange(
- resources.getString(
- commitMessageRes,
- getLongName(fullPath, repoPath, editName)
- )
- )
- .onSuccess {
- setResult(RESULT_OK, returnIntent)
- finish()
- }
- }
- }
- .onFailure { e ->
- if (e is IOException) {
- logcat(ERROR) { e.asLog("Failed to write password file") }
- setResult(RESULT_CANCELED)
- MaterialAlertDialogBuilder(this@PasswordCreationActivity)
- .setTitle(getString(R.string.password_creation_file_fail_title))
- .setMessage(getString(R.string.password_creation_file_write_fail_message))
- .setCancelable(false)
- .setPositiveButton(android.R.string.ok) { _, _ -> finish() }
- .show()
- } else {
- logcat(ERROR) { e.asLog() }
- }
- }
- }
- OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val sender = getUserInteractionRequestIntent(result)
- userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
- }
- OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
- }
- }
- }
- }
-
- companion object {
-
- private const val KEY_PWGEN_TYPE_CLASSIC = "classic"
- private const val KEY_PWGEN_TYPE_DICEWARE = "diceware"
- const val PASSWORD_RESULT_REQUEST_KEY = "PASSWORD_GENERATOR"
- const val OTP_RESULT_REQUEST_KEY = "OTP_IMPORT"
- const val RESULT = "RESULT"
- const val RETURN_EXTRA_CREATED_FILE = "CREATED_FILE"
- const val RETURN_EXTRA_NAME = "NAME"
- const val RETURN_EXTRA_LONG_NAME = "LONG_NAME"
- const val RETURN_EXTRA_USERNAME = "USERNAME"
- const val RETURN_EXTRA_PASSWORD = "PASSWORD"
- const val EXTRA_FILE_NAME = "FILENAME"
- const val EXTRA_PASSWORD = "PASSWORD"
- const val EXTRA_EXTRA_CONTENT = "EXTRA_CONTENT"
- const val EXTRA_GENERATE_PASSWORD = "GENERATE_PASSWORD"
- const val EXTRA_EDITING = "EDITING"
- }
-}
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivityV2.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivityV2.kt
index 7070ce7b..50a6e825 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivityV2.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivityV2.kt
@@ -134,7 +134,6 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
- bindToOpenKeychain(this)
title =
if (editing) getString(R.string.edit_password) else getString(R.string.new_password_title)
with(binding) {
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt
index bb3b6f6f..04488e90 100644
--- a/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/dialogs/DicewarePasswordGeneratorDialogFragment.kt
@@ -19,7 +19,7 @@ import app.passwordstore.R
import app.passwordstore.databinding.FragmentPwgenDicewareBinding
import app.passwordstore.injection.prefs.PasswordGeneratorPreferences
import app.passwordstore.passgen.diceware.DicewarePassphraseGenerator
-import app.passwordstore.ui.crypto.PasswordCreationActivity
+import app.passwordstore.ui.crypto.PasswordCreationActivityV2
import app.passwordstore.util.extensions.getString
import app.passwordstore.util.settings.PreferenceKeys.DICEWARE_LENGTH
import app.passwordstore.util.settings.PreferenceKeys.DICEWARE_SEPARATOR
@@ -58,8 +58,8 @@ class DicewarePasswordGeneratorDialogFragment : DialogFragment() {
setTitle(R.string.pwgen_title)
setPositiveButton(R.string.dialog_ok) { _, _ ->
setFragmentResult(
- PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
+ PasswordCreationActivityV2.PASSWORD_RESULT_REQUEST_KEY,
+ bundleOf(PasswordCreationActivityV2.RESULT to "${binding.passwordText.text}")
)
}
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt
index 78177f10..cac3bcd7 100644
--- a/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/dialogs/FolderCreationDialogFragment.kt
@@ -5,60 +5,21 @@
package app.passwordstore.ui.dialogs
import android.app.Dialog
-import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
-import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.crypto.BasePgpActivity
-import app.passwordstore.ui.crypto.GetKeyIdsActivity
import app.passwordstore.ui.passwords.PasswordStore
-import app.passwordstore.util.extensions.commitChange
-import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import java.io.File
-import kotlinx.coroutines.launch
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
class FolderCreationDialogFragment : DialogFragment() {
private lateinit var newFolder: File
-
- private val keySelectAction =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == AppCompatActivity.RESULT_OK) {
- result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
- val gpgIdentifierFile = File(newFolder, ".gpg-id")
- gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
- if (PasswordRepository.repository != null) {
- lifecycleScope.launch {
- val repoPath = PasswordRepository.getRepositoryDirectory().absolutePath
- requireActivity()
- .commitChange(
- getString(
- R.string.git_commit_gpg_id,
- BasePgpActivity.getLongName(
- gpgIdentifierFile.parentFile!!.absolutePath,
- repoPath,
- gpgIdentifierFile.name
- )
- ),
- )
- dismiss()
- }
- }
- }
- }
- }
-
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
alertDialogBuilder.setTitle(R.string.title_create_folder)
@@ -89,12 +50,16 @@ class FolderCreationDialogFragment : DialogFragment() {
if (folderNameViewContainer.error != null) return
newFolder.mkdirs()
(requireActivity() as PasswordStore).refreshPasswordList(newFolder)
- if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) {
- keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
- return
- } else {
- dismiss()
- }
+ // TODO(msfjarvis): Restore this functionality
+ /*
+ if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) {
+ keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
+ return
+ } else {
+ dismiss()
+ }
+ */
+ dismiss()
}
companion object {
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/OtpImportDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/OtpImportDialogFragment.kt
index 4d2413d4..cd36be04 100644
--- a/app/src/main/java/app/passwordstore/ui/dialogs/OtpImportDialogFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/dialogs/OtpImportDialogFragment.kt
@@ -13,7 +13,7 @@ import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import app.passwordstore.databinding.FragmentManualOtpEntryBinding
-import app.passwordstore.ui.crypto.PasswordCreationActivity
+import app.passwordstore.ui.crypto.PasswordCreationActivityV2
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class OtpImportDialogFragment : DialogFragment() {
@@ -24,8 +24,8 @@ class OtpImportDialogFragment : DialogFragment() {
builder.setView(binding.root)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
setFragmentResult(
- PasswordCreationActivity.OTP_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to getTOTPUri(binding))
+ PasswordCreationActivityV2.OTP_RESULT_REQUEST_KEY,
+ bundleOf(PasswordCreationActivityV2.RESULT to getTOTPUri(binding))
)
}
val dialog = builder.create()
diff --git a/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
index 06ce2d92..352c755f 100644
--- a/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt
@@ -26,7 +26,7 @@ import app.passwordstore.passgen.random.NoCharactersIncludedException
import app.passwordstore.passgen.random.PasswordGenerator
import app.passwordstore.passgen.random.PasswordLengthTooShortException
import app.passwordstore.passgen.random.PasswordOption
-import app.passwordstore.ui.crypto.PasswordCreationActivity
+import app.passwordstore.ui.crypto.PasswordCreationActivityV2
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.runCatching
@@ -72,8 +72,8 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
setTitle(R.string.pwgen_title)
setPositiveButton(R.string.dialog_ok) { _, _ ->
setFragmentResult(
- PasswordCreationActivity.PASSWORD_RESULT_REQUEST_KEY,
- bundleOf(PasswordCreationActivity.RESULT to "${binding.passwordText.text}")
+ PasswordCreationActivityV2.PASSWORD_RESULT_REQUEST_KEY,
+ bundleOf(PasswordCreationActivityV2.RESULT to "${binding.passwordText.text}")
)
}
setNeutralButton(R.string.dialog_cancel) { _, _ -> }
diff --git a/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt b/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt
index fb9cfde3..b79a1425 100644
--- a/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/git/base/BaseGitActivity.kt
@@ -18,7 +18,6 @@ import app.passwordstore.util.git.operation.PullOperation
import app.passwordstore.util.git.operation.PushOperation
import app.passwordstore.util.git.operation.ResetToRemoteOperation
import app.passwordstore.util.git.operation.SyncOperation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
import app.passwordstore.util.settings.GitSettings
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.Err
@@ -42,7 +41,7 @@ import net.schmizz.sshj.userauth.UserAuthException
* git-related tasks and makes sense to be held here.
*/
@AndroidEntryPoint
-abstract class BaseGitActivity : ContinuationContainerActivity() {
+abstract class BaseGitActivity : AppCompatActivity() {
/** Enum of possible Git operations than can be run through [launchGitOperation]. */
enum class GitOp {
diff --git a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt b/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt
index 9cac0fdd..a7ecd697 100644
--- a/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/git/config/GitServerConfigActivity.kt
@@ -62,7 +62,6 @@ class GitServerConfigActivity : BaseGitActivity() {
when (newAuthMode) {
AuthMode.SshKey -> check(binding.authModeSshKey.id)
AuthMode.Password -> check(binding.authModePassword.id)
- AuthMode.OpenKeychain -> check(binding.authModeOpenKeychain.id)
AuthMode.None -> check(View.NO_ID)
}
addOnButtonCheckedListener { _, checkedId, isChecked ->
@@ -72,7 +71,6 @@ class GitServerConfigActivity : BaseGitActivity() {
}
when (checkedId) {
binding.authModeSshKey.id -> newAuthMode = AuthMode.SshKey
- binding.authModeOpenKeychain.id -> newAuthMode = AuthMode.OpenKeychain
binding.authModePassword.id -> newAuthMode = AuthMode.Password
View.NO_ID -> newAuthMode = AuthMode.None
}
@@ -215,12 +213,10 @@ class GitServerConfigActivity : BaseGitActivity() {
with(binding) {
if (isHttps) {
authModeSshKey.isVisible = false
- authModeOpenKeychain.isVisible = false
authModePassword.isVisible = true
if (authModeGroup.checkedButtonId != authModePassword.id) authModeGroup.check(View.NO_ID)
} else {
authModeSshKey.isVisible = true
- authModeOpenKeychain.isVisible = true
authModePassword.isVisible = true
if (authModeGroup.checkedButtonId == View.NO_ID) authModeGroup.check(authModeSshKey.id)
}
diff --git a/app/src/main/java/app/passwordstore/ui/main/LaunchActivity.kt b/app/src/main/java/app/passwordstore/ui/main/LaunchActivity.kt
index d2a34c80..1a2e1a60 100644
--- a/app/src/main/java/app/passwordstore/ui/main/LaunchActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/main/LaunchActivity.kt
@@ -11,13 +11,11 @@ import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import app.passwordstore.ui.crypto.BasePgpActivity
-import app.passwordstore.ui.crypto.DecryptActivity
import app.passwordstore.ui.crypto.DecryptActivityV2
import app.passwordstore.ui.passwords.PasswordStore
import app.passwordstore.util.auth.BiometricAuthenticator
import app.passwordstore.util.auth.BiometricAuthenticator.Result
import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.features.Feature
import app.passwordstore.util.features.Features
import app.passwordstore.util.settings.PreferenceKeys
import dagger.hilt.android.AndroidEntryPoint
diff --git a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt
index 2b32cb61..43dc7f4c 100644
--- a/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt
+++ b/app/src/main/java/app/passwordstore/ui/onboarding/fragments/KeySelectionFragment.kt
@@ -5,65 +5,21 @@
package app.passwordstore.ui.onboarding.fragments
-import android.content.Intent
import android.os.Bundle
import android.view.View
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.edit
import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
-import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.databinding.FragmentKeySelectionBinding
-import app.passwordstore.ui.crypto.GetKeyIdsActivity
-import app.passwordstore.util.extensions.commitChange
-import app.passwordstore.util.extensions.finish
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.extensions.snackbar
-import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.extensions.viewBinding
-import app.passwordstore.util.settings.PreferenceKeys
-import com.google.android.material.snackbar.Snackbar
-import java.io.File
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) {
- private val settings by unsafeLazy { requireActivity().applicationContext.sharedPrefs }
private val binding by viewBinding(FragmentKeySelectionBinding::bind)
-
- private val gpgKeySelectAction =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == AppCompatActivity.RESULT_OK) {
- result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
- lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id")
- gpgIdentifierFile.writeText((keyIds + "").joinToString("\n"))
- }
- settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
- requireActivity()
- .commitChange(getString(R.string.git_commit_gpg_id, getString(R.string.app_name)))
- }
- }
- finish()
- } else {
- requireActivity()
- .snackbar(
- message = getString(R.string.gpg_key_select_mandatory),
- length = Snackbar.LENGTH_LONG
- )
- }
- }
-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.selectKey.setOnClickListener {
- gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
+ // TODO(msfjarvis): Restore this functionality
+ // gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
}
}
diff --git a/app/src/main/java/app/passwordstore/util/crypto/GpgIdentifier.kt b/app/src/main/java/app/passwordstore/util/crypto/GpgIdentifier.kt
deleted file mode 100644
index 3df3e09f..00000000
--- a/app/src/main/java/app/passwordstore/util/crypto/GpgIdentifier.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.util.crypto
-
-import me.msfjarvis.openpgpktx.util.OpenPgpUtils
-
-sealed class GpgIdentifier {
- data class KeyId(val id: Long) : GpgIdentifier()
- data class UserId(val email: String) : GpgIdentifier()
-
- companion object {
- @OptIn(ExperimentalUnsignedTypes::class)
- fun fromString(identifier: String): GpgIdentifier? {
- if (identifier.isEmpty()) return null
- // Match long key IDs:
- // FF22334455667788 or 0xFF22334455667788
- val maybeLongKeyId =
- identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{16}".toRegex()) }
- if (maybeLongKeyId != null) {
- val keyId = maybeLongKeyId.toULong(16)
- return KeyId(keyId.toLong())
- }
-
- // Match fingerprints:
- // FF223344556677889900112233445566778899 or 0xFF223344556677889900112233445566778899
- val maybeFingerprint =
- identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F0-9]{40}".toRegex()) }
- if (maybeFingerprint != null) {
- // Truncating to the long key ID is not a security issue since OpenKeychain only
- // accepts
- // non-ambiguous key IDs.
- val keyId = maybeFingerprint.takeLast(16).toULong(16)
- return KeyId(keyId.toLong())
- }
-
- return OpenPgpUtils.splitUserId(identifier).email?.let { UserId(it) }
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt b/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt
index 65629daa..2ce6f625 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt
@@ -4,16 +4,15 @@
*/
package app.passwordstore.util.git.operation
+import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.R
import app.passwordstore.util.extensions.unsafeLazy
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.lib.RepositoryState
-class BreakOutOfDetached(callingActivity: ContinuationContainerActivity) :
- GitOperation(callingActivity) {
+class BreakOutOfDetached(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
private val merging = repository.repositoryState == RepositoryState.MERGING
private val resetCommands =
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/CloneOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/CloneOperation.kt
index 88178125..f0348771 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/CloneOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/CloneOperation.kt
@@ -4,7 +4,7 @@
*/
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.GitCommand
@@ -14,7 +14,7 @@ import org.eclipse.jgit.api.GitCommand
* @param uri URL to clone the repository from
* @param callingActivity the calling activity
*/
-class CloneOperation(callingActivity: ContinuationContainerActivity, uri: String) :
+class CloneOperation(callingActivity: AppCompatActivity, uri: String) :
GitOperation(callingActivity) {
override val commands: Array<GitCommand<out Any>> =
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/GcOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/GcOperation.kt
index 556c899e..69535fc3 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/GcOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/GcOperation.kt
@@ -5,7 +5,7 @@
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.GitCommand
/**
@@ -13,7 +13,7 @@ import org.eclipse.jgit.api.GitCommand
* achieve the best compression.
*/
class GcOperation(
- callingActivity: ContinuationContainerActivity,
+ callingActivity: AppCompatActivity,
) : GitOperation(callingActivity) {
override val requiresAuth: Boolean = false
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 2f13b5ad..02cea621 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
@@ -6,6 +6,7 @@ package app.passwordstore.util.git.operation
import android.content.Intent
import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository
@@ -14,7 +15,6 @@ import app.passwordstore.ui.sshkeygen.SshKeyImportActivity
import app.passwordstore.util.auth.BiometricAuthenticator
import app.passwordstore.util.auth.BiometricAuthenticator.Result.*
import app.passwordstore.util.git.GitCommandExecutor
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
import app.passwordstore.util.git.sshj.SshAuthMethod
import app.passwordstore.util.git.sshj.SshKey
import app.passwordstore.util.git.sshj.SshjSessionFactory
@@ -74,7 +74,7 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
protected val git = Git(repository)
protected val remoteBranch = hiltEntryPoint.gitSettings().branch
private val authActivity
- get() = callingActivity as ContinuationContainerActivity
+ get() = callingActivity as AppCompatActivity
private class HttpsCredentialsProvider(private val passwordFinder: PasswordFinder) :
CredentialsProvider() {
@@ -213,7 +213,6 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
// error, allowing users to make the SSH key selection.
return Err(SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER))
}
- AuthMode.OpenKeychain -> registerAuthProviders(SshAuthMethod.OpenKeychain(authActivity))
AuthMode.Password -> {
val httpsCredentialProvider =
HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password))
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/PullOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/PullOperation.kt
index 75b6fc1a..a211a2f7 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/PullOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/PullOperation.kt
@@ -4,11 +4,11 @@
*/
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.GitCommand
class PullOperation(
- callingActivity: ContinuationContainerActivity,
+ callingActivity: AppCompatActivity,
rebase: Boolean,
) : GitOperation(callingActivity) {
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/PushOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/PushOperation.kt
index 386d79e6..fa2dd1ba 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/PushOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/PushOperation.kt
@@ -4,11 +4,10 @@
*/
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.GitCommand
-class PushOperation(callingActivity: ContinuationContainerActivity) :
- GitOperation(callingActivity) {
+class PushOperation(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
override val commands: Array<GitCommand<out Any>> =
arrayOf(
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt
index 7c8cee93..ddbdc807 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt
@@ -4,11 +4,10 @@
*/
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.ResetCommand
-class ResetToRemoteOperation(callingActivity: ContinuationContainerActivity) :
- GitOperation(callingActivity) {
+class ResetToRemoteOperation(callingActivity: AppCompatActivity) : GitOperation(callingActivity) {
override val commands =
arrayOf(
diff --git a/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt b/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt
index 226fd753..e36b01b1 100644
--- a/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt
+++ b/app/src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt
@@ -4,10 +4,10 @@
*/
package app.passwordstore.util.git.operation
-import app.passwordstore.util.git.sshj.ContinuationContainerActivity
+import androidx.appcompat.app.AppCompatActivity
class SyncOperation(
- callingActivity: ContinuationContainerActivity,
+ callingActivity: AppCompatActivity,
rebase: Boolean,
) : GitOperation(callingActivity) {
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/ContinuationContainerActivity.kt b/app/src/main/java/app/passwordstore/util/git/sshj/ContinuationContainerActivity.kt
deleted file mode 100644
index 10872a24..00000000
--- a/app/src/main/java/app/passwordstore/util/git/sshj/ContinuationContainerActivity.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.util.git.sshj
-
-import android.content.Intent
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.LayoutRes
-import androidx.appcompat.app.AppCompatActivity
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-import net.schmizz.sshj.common.DisconnectReason
-import net.schmizz.sshj.userauth.UserAuthException
-
-/** Workaround for https://msfjarvis.dev/aps/issue/1164 */
-open class ContinuationContainerActivity : AppCompatActivity {
-
- constructor() : super()
- constructor(@LayoutRes layoutRes: Int) : super(layoutRes)
-
- var stashedCont: Continuation<Intent>? = null
-
- val continueAfterUserInteraction =
- registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
- stashedCont?.let { cont ->
- stashedCont = null
- val data = result.data
- if (data != null) cont.resume(data)
- else cont.resumeWithException(UserAuthException(DisconnectReason.AUTH_CANCELLED_BY_USER))
- }
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainKeyProvider.kt b/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainKeyProvider.kt
deleted file mode 100644
index 7603059f..00000000
--- a/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainKeyProvider.kt
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.util.git.sshj
-
-import android.app.PendingIntent
-import android.content.Intent
-import androidx.activity.result.IntentSenderRequest
-import androidx.core.content.edit
-import androidx.lifecycle.lifecycleScope
-import app.passwordstore.util.extensions.OPENPGP_PROVIDER
-import app.passwordstore.util.extensions.sharedPrefs
-import app.passwordstore.util.settings.PreferenceKeys
-import java.io.Closeable
-import java.security.PublicKey
-import java.security.interfaces.ECKey
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.logcat
-import net.schmizz.sshj.common.DisconnectReason
-import net.schmizz.sshj.common.KeyType
-import net.schmizz.sshj.userauth.UserAuthException
-import net.schmizz.sshj.userauth.keyprovider.KeyProvider
-import org.openintents.ssh.authentication.ISshAuthenticationService
-import org.openintents.ssh.authentication.SshAuthenticationApi
-import org.openintents.ssh.authentication.SshAuthenticationApiError
-import org.openintents.ssh.authentication.SshAuthenticationConnection
-import org.openintents.ssh.authentication.request.KeySelectionRequest
-import org.openintents.ssh.authentication.request.Request
-import org.openintents.ssh.authentication.request.SigningRequest
-import org.openintents.ssh.authentication.request.SshPublicKeyRequest
-import org.openintents.ssh.authentication.response.KeySelectionResponse
-import org.openintents.ssh.authentication.response.Response
-import org.openintents.ssh.authentication.response.SigningResponse
-import org.openintents.ssh.authentication.response.SshPublicKeyResponse
-
-class OpenKeychainKeyProvider private constructor(val activity: ContinuationContainerActivity) :
- KeyProvider, Closeable {
-
- companion object {
-
- suspend fun prepareAndUse(
- activity: ContinuationContainerActivity,
- block: (provider: OpenKeychainKeyProvider) -> Unit
- ) {
- withContext(Dispatchers.Main) { OpenKeychainKeyProvider(activity) }.prepareAndUse(block)
- }
- }
-
- private sealed class ApiResponse {
- data class Success(val response: Response) : ApiResponse()
- data class GeneralError(val exception: Exception) : ApiResponse()
- data class NoSuchKey(val exception: Exception) : ApiResponse()
- }
-
- private val context = activity.applicationContext
- private val sshServiceConnection = SshAuthenticationConnection(context, OPENPGP_PROVIDER)
- private val preferences = context.sharedPrefs
- private lateinit var sshServiceApi: SshAuthenticationApi
-
- private var keyId
- get() = preferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null)
- set(value) {
- preferences.edit { putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, value) }
- }
- private var publicKey: PublicKey? = null
- private var privateKey: OpenKeychainPrivateKey? = null
-
- private suspend fun prepareAndUse(block: (provider: OpenKeychainKeyProvider) -> Unit) {
- prepare()
- use(block)
- }
-
- private suspend fun prepare() {
- sshServiceApi = suspendCoroutine { cont ->
- sshServiceConnection.connect(
- object : SshAuthenticationConnection.OnBound {
- override fun onBound(sshAgent: ISshAuthenticationService) {
- logcat { "Bound to SshAuthenticationApi: $OPENPGP_PROVIDER" }
- cont.resume(SshAuthenticationApi(context, sshAgent))
- }
-
- override fun onError() {
- throw UserAuthException(DisconnectReason.UNKNOWN, "OpenKeychain service unavailable")
- }
- }
- )
- }
-
- if (keyId == null) {
- selectKey()
- }
- check(keyId != null)
- fetchPublicKey()
- makePrivateKey()
- }
-
- private suspend fun fetchPublicKey(isRetry: Boolean = false) {
- when (val sshPublicKeyResponse = executeApiRequest(SshPublicKeyRequest(keyId))) {
- is ApiResponse.Success -> {
- val response = sshPublicKeyResponse.response as SshPublicKeyResponse
- val sshPublicKey = response.sshPublicKey!!
- publicKey =
- parseSshPublicKey(sshPublicKey)
- ?: throw IllegalStateException("OpenKeychain API returned invalid SSH key")
- }
- is ApiResponse.NoSuchKey ->
- if (isRetry) {
- throw sshPublicKeyResponse.exception
- } else {
- // Allow the user to reselect an authentication key and retry
- selectKey()
- fetchPublicKey(true)
- }
- is ApiResponse.GeneralError -> throw sshPublicKeyResponse.exception
- }
- }
-
- private suspend fun selectKey() {
- when (val keySelectionResponse = executeApiRequest(KeySelectionRequest())) {
- is ApiResponse.Success ->
- keyId = (keySelectionResponse.response as KeySelectionResponse).keyId
- is ApiResponse.GeneralError -> throw keySelectionResponse.exception
- is ApiResponse.NoSuchKey -> throw keySelectionResponse.exception
- }
- }
-
- private suspend fun executeApiRequest(
- request: Request,
- resultOfUserInteraction: Intent? = null
- ): ApiResponse {
- logcat { "executeRequest($request) called" }
- val result =
- withContext(Dispatchers.Main) {
- // If the request required user interaction, the data returned from the
- // PendingIntent
- // is used as the real request.
- sshServiceApi.executeApi(resultOfUserInteraction ?: request.toIntent())!!
- }
- return parseResult(request, result).also { logcat { "executeRequest($request): $it" } }
- }
-
- private suspend fun parseResult(request: Request, result: Intent): ApiResponse {
- return when (
- result.getIntExtra(
- SshAuthenticationApi.EXTRA_RESULT_CODE,
- SshAuthenticationApi.RESULT_CODE_ERROR
- )
- ) {
- SshAuthenticationApi.RESULT_CODE_SUCCESS -> {
- ApiResponse.Success(
- when (request) {
- is KeySelectionRequest -> KeySelectionResponse(result)
- is SshPublicKeyRequest -> SshPublicKeyResponse(result)
- is SigningRequest -> SigningResponse(result)
- else -> throw IllegalArgumentException("Unsupported OpenKeychain request type")
- }
- )
- }
- SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val pendingIntent: PendingIntent =
- result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT)!!
- val resultOfUserInteraction: Intent =
- withContext(Dispatchers.Main) {
- suspendCoroutine { cont ->
- activity.stashedCont = cont
- activity.continueAfterUserInteraction.launch(
- IntentSenderRequest.Builder(pendingIntent).build()
- )
- }
- }
- executeApiRequest(request, resultOfUserInteraction)
- }
- else -> {
- val error =
- result.getParcelableExtra<SshAuthenticationApiError>(SshAuthenticationApi.EXTRA_ERROR)
- val exception =
- UserAuthException(
- DisconnectReason.UNKNOWN,
- "Request ${request::class.simpleName} failed: ${error?.message}"
- )
- when (error?.error) {
- SshAuthenticationApiError.NO_AUTH_KEY,
- SshAuthenticationApiError.NO_SUCH_KEY -> ApiResponse.NoSuchKey(exception)
- else -> ApiResponse.GeneralError(exception)
- }
- }
- }
- }
-
- private fun makePrivateKey() {
- check(keyId != null && publicKey != null)
- privateKey =
- object : OpenKeychainPrivateKey {
- override suspend fun sign(challenge: ByteArray, hashAlgorithm: Int) =
- when (
- val signingResponse = executeApiRequest(SigningRequest(challenge, keyId, hashAlgorithm))
- ) {
- is ApiResponse.Success -> (signingResponse.response as SigningResponse).signature
- is ApiResponse.GeneralError -> throw signingResponse.exception
- is ApiResponse.NoSuchKey -> throw signingResponse.exception
- }
-
- override fun getAlgorithm() = publicKey!!.algorithm
- override fun getParams() = (publicKey as? ECKey)?.params
- }
- }
-
- override fun close() {
- activity.lifecycleScope.launch {
- withContext(Dispatchers.Main) { activity.continueAfterUserInteraction.unregister() }
- }
- sshServiceConnection.disconnect()
- }
-
- override fun getPrivate() = privateKey
-
- override fun getPublic() = publicKey
-
- override fun getType(): KeyType = KeyType.fromKey(publicKey)
-}
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt b/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt
deleted file mode 100644
index 611ba9e0..00000000
--- a/app/src/main/java/app/passwordstore/util/git/sshj/OpenKeychainWrappedKeyAlgorithmFactory.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package app.passwordstore.util.git.sshj
-
-import com.hierynomus.sshj.key.KeyAlgorithm
-import java.io.ByteArrayOutputStream
-import java.security.PrivateKey
-import java.security.interfaces.ECKey
-import kotlinx.coroutines.runBlocking
-import net.schmizz.sshj.common.Buffer
-import net.schmizz.sshj.common.Factory
-import net.schmizz.sshj.signature.Signature
-import org.openintents.ssh.authentication.SshAuthenticationApi
-
-interface OpenKeychainPrivateKey : PrivateKey, ECKey {
-
- suspend fun sign(challenge: ByteArray, hashAlgorithm: Int): ByteArray
-
- override fun getFormat() = null
- override fun getEncoded() = null
-}
-
-class OpenKeychainWrappedKeyAlgorithmFactory(private val factory: Factory.Named<KeyAlgorithm>) :
- Factory.Named<KeyAlgorithm> by factory {
-
- override fun create() = OpenKeychainWrappedKeyAlgorithm(factory.create())
-}
-
-class OpenKeychainWrappedKeyAlgorithm(private val keyAlgorithm: KeyAlgorithm) :
- KeyAlgorithm by keyAlgorithm {
-
- private val hashAlgorithm =
- when (keyAlgorithm.keyAlgorithm) {
- "rsa-sha2-512" -> SshAuthenticationApi.SHA512
- "rsa-sha2-256" -> SshAuthenticationApi.SHA256
- "ssh-rsa",
- "ssh-rsa-cert-v01@openssh.com" -> SshAuthenticationApi.SHA1
- // Other algorithms don't use this value, but it has to be valid.
- else -> SshAuthenticationApi.SHA512
- }
-
- override fun newSignature() =
- OpenKeychainWrappedSignature(keyAlgorithm.newSignature(), hashAlgorithm)
-}
-
-class OpenKeychainWrappedSignature(
- private val wrappedSignature: Signature,
- private val hashAlgorithm: Int
-) : Signature by wrappedSignature {
-
- private val data = ByteArrayOutputStream()
-
- private var bridgedPrivateKey: OpenKeychainPrivateKey? = null
-
- override fun initSign(prvkey: PrivateKey?) {
- if (prvkey is OpenKeychainPrivateKey) {
- bridgedPrivateKey = prvkey
- } else {
- wrappedSignature.initSign(prvkey)
- }
- }
-
- override fun update(H: ByteArray?) {
- if (bridgedPrivateKey != null) {
- data.write(H!!)
- } else {
- wrappedSignature.update(H)
- }
- }
-
- override fun update(H: ByteArray?, off: Int, len: Int) {
- if (bridgedPrivateKey != null) {
- data.write(H!!, off, len)
- } else {
- wrappedSignature.update(H, off, len)
- }
- }
-
- override fun sign(): ByteArray? =
- if (bridgedPrivateKey != null) {
- runBlocking { bridgedPrivateKey!!.sign(data.toByteArray(), hashAlgorithm) }
- } else {
- wrappedSignature.sign()
- }
-
- override fun encode(signature: ByteArray?): ByteArray? =
- if (bridgedPrivateKey != null) {
- require(signature != null) { "OpenKeychain signature must not be null" }
- val encodedSignature = Buffer.PlainBuffer(signature)
- // We need to drop the algorithm name and extract the raw signature since SSHJ adds the
- // name
- // later.
- encodedSignature.readString()
- encodedSignature.readBytes().also {
- bridgedPrivateKey = null
- data.reset()
- }
- } else {
- wrappedSignature.encode(signature)
- }
-}
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt b/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
index 7522de50..5601a253 100644
--- a/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
+++ b/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
@@ -232,16 +232,15 @@ class SshjConfig : ConfigImpl() {
private fun initKeyAlgorithms() {
keyAlgorithms =
listOf(
- KeyAlgorithms.SSHRSACertV01(),
- KeyAlgorithms.EdDSA25519(),
- KeyAlgorithms.ECDSASHANistp521(),
- KeyAlgorithms.ECDSASHANistp384(),
- KeyAlgorithms.ECDSASHANistp256(),
- KeyAlgorithms.RSASHA512(),
- KeyAlgorithms.RSASHA256(),
- KeyAlgorithms.SSHRSA(),
- )
- .map { OpenKeychainWrappedKeyAlgorithmFactory(it) }
+ KeyAlgorithms.SSHRSACertV01(),
+ KeyAlgorithms.EdDSA25519(),
+ KeyAlgorithms.ECDSASHANistp521(),
+ KeyAlgorithms.ECDSASHANistp384(),
+ KeyAlgorithms.ECDSASHANistp256(),
+ KeyAlgorithms.RSASHA512(),
+ KeyAlgorithms.RSASHA256(),
+ KeyAlgorithms.SSHRSA(),
+ )
}
private fun initRandomFactory() {
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 b7f0542f..58af8495 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
@@ -5,6 +5,7 @@
package app.passwordstore.util.git.sshj
import android.util.Base64
+import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.util.git.operation.CredentialFinder
import app.passwordstore.util.settings.AuthMode
import com.github.michaelbull.result.getOrElse
@@ -41,10 +42,9 @@ import org.eclipse.jgit.transport.SshSessionFactory
import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.util.FS
-sealed class SshAuthMethod(val activity: ContinuationContainerActivity) {
- class Password(activity: ContinuationContainerActivity) : SshAuthMethod(activity)
- class SshKey(activity: ContinuationContainerActivity) : SshAuthMethod(activity)
- class OpenKeychain(activity: ContinuationContainerActivity) : SshAuthMethod(activity)
+sealed class SshAuthMethod(val activity: AppCompatActivity) {
+ class Password(activity: AppCompatActivity) : SshAuthMethod(activity)
+ class SshKey(activity: AppCompatActivity) : SshAuthMethod(activity)
}
abstract class InteractivePasswordFinder : PasswordFinder {
@@ -157,14 +157,6 @@ private class SshjSession(
AuthPublickey(SshKey.provide(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)))
ssh.auth(username, pubkeyAuth, passwordAuth)
}
- is SshAuthMethod.OpenKeychain -> {
- runBlocking {
- OpenKeychainKeyProvider.prepareAndUse(authMethod.activity) { provider ->
- val openKeychainAuth = AuthPublickey(provider)
- ssh.auth(username, openKeychainAuth, passwordAuth)
- }
- }
- }
}
return this
}
diff --git a/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt b/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt
index 1baf9640..ed1c02dc 100644
--- a/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt
+++ b/app/src/main/java/app/passwordstore/util/settings/GitSettings.kt
@@ -37,7 +37,6 @@ enum class Protocol(val pref: String) {
enum class AuthMode(val pref: String) {
SshKey("ssh-key"),
Password("username/password"),
- OpenKeychain("OpenKeychain"),
None("None"),
;
@@ -156,7 +155,7 @@ constructor(
)
return UpdateConnectionSettingsResult.MissingUsername(newProtocol)
val validHttpsAuth = listOf(AuthMode.None, AuthMode.Password)
- val validSshAuth = listOf(AuthMode.OpenKeychain, AuthMode.Password, AuthMode.SshKey)
+ val validSshAuth = listOf(AuthMode.Password, AuthMode.SshKey)
when {
newProtocol == Protocol.Https && newAuthMode !in validHttpsAuth -> {
return UpdateConnectionSettingsResult.AuthModeMismatch(newProtocol, validHttpsAuth)
diff --git a/app/src/main/res/layout/activity_git_clone.xml b/app/src/main/res/layout/activity_git_clone.xml
index 269c5460..16998921 100644
--- a/app/src/main/res/layout/activity_git_clone.xml
+++ b/app/src/main/res/layout/activity_git_clone.xml
@@ -89,12 +89,6 @@
android:layout_height="wrap_content"
android:text="@string/connection_mode_basic_authentication" />
- <com.google.android.material.button.MaterialButton
- android:id="@+id/auth_mode_open_keychain"
- style="?attr/materialButtonOutlinedStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/connection_mode_openkeychain" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<com.google.android.material.button.MaterialButton
diff --git a/app/src/main/res/layout/decrypt_layout.xml b/app/src/main/res/layout/decrypt_layout.xml
index a5fbbeec..61cf6d12 100644
--- a/app/src/main/res/layout/decrypt_layout.xml
+++ b/app/src/main/res/layout/decrypt_layout.xml
@@ -10,7 +10,7 @@
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="16dp"
- tools:context="app.passwordstore.ui.crypto.DecryptActivity">
+ tools:context="app.passwordstore.ui.crypto.DecryptActivityV2">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/password_category"
diff --git a/app/src/main/res/layout/folder_dialog_fragment.xml b/app/src/main/res/layout/folder_dialog_fragment.xml
index dad9f8a6..7d7002f9 100644
--- a/app/src/main/res/layout/folder_dialog_fragment.xml
+++ b/app/src/main/res/layout/folder_dialog_fragment.xml
@@ -26,9 +26,11 @@
<requestFocus />
</com.google.android.material.textfield.TextInputLayout>
+ <!-- TODO(msfjarvis): Restore this functionality -->
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/set_gpg_key"
android:layout_width="0dp"
+ android:visibility="gone"
android:layout_height="wrap_content"
android:text="@string/new_folder_set_gpg_key"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/password_creation_activity.xml b/app/src/main/res/layout/password_creation_activity.xml
index c489e121..f990d0c8 100644
--- a/app/src/main/res/layout/password_creation_activity.xml
+++ b/app/src/main/res/layout/password_creation_activity.xml
@@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false"
- tools:context="app.passwordstore.ui.crypto.PasswordCreationActivity">
+ tools:context="app.passwordstore.ui.crypto.PasswordCreationActivityV2">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
diff --git a/app/src/test/java/app/passwordstore/util/crypto/GpgIdentifierTest.kt b/app/src/test/java/app/passwordstore/util/crypto/GpgIdentifierTest.kt
deleted file mode 100644
index 29a9dc16..00000000
--- a/app/src/test/java/app/passwordstore/util/crypto/GpgIdentifierTest.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.util.crypto
-
-import kotlin.test.Test
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
-
-class GpgIdentifierTest {
-
- @Test
- fun parseHexKeyIdWithout0xPrefix() {
- val identifier = GpgIdentifier.fromString("79E8208280490C77")
- assertNotNull(identifier)
- assertTrue { identifier is GpgIdentifier.KeyId }
- }
-
- @Test
- fun parseHexKeyId() {
- val identifier = GpgIdentifier.fromString("0x79E8208280490C77")
- assertNotNull(identifier)
- assertTrue { identifier is GpgIdentifier.KeyId }
- }
-
- @Test
- fun parseValidEmail() {
- val identifier = GpgIdentifier.fromString("john.doe@example.org")
- assertNotNull(identifier)
- assertTrue { identifier is GpgIdentifier.UserId }
- }
-
- @Test
- fun parseEmailWithoutTLD() {
- val identifier = GpgIdentifier.fromString("john.doe@example")
- assertNotNull(identifier)
- assertTrue { identifier is GpgIdentifier.UserId }
- }
-}