diff options
Diffstat (limited to 'app/src/main/java')
8 files changed, 112 insertions, 192 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index 5d76eef0..57e636d1 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -416,17 +416,6 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) { return // if not empty, just show me the passwords! } } - val keyIds = settings.getStringSet(PreferenceKeys.OPENPGP_KEY_IDS_SET, HashSet()) - if (keyIds != null && keyIds.isEmpty()) { - MaterialAlertDialogBuilder(this) - .setMessage(resources.getString(R.string.key_dialog_text)) - .setPositiveButton(resources.getString(R.string.dialog_positive)) { _, _ -> - val intent = Intent(activity, UserPreference::class.java) - repositoryInitAction.launch(intent) - } - .setNegativeButton(resources.getString(R.string.dialog_negative), null) - .show() - } createRepository() } @@ -580,17 +569,6 @@ class PasswordStore : AppCompatActivity(R.layout.activity_pwdstore) { .show() return false } - if (settings.getStringSet(PreferenceKeys.OPENPGP_KEY_IDS_SET, HashSet()).isNullOrEmpty()) { - MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.no_key_selected_dialog_title)) - .setMessage(resources.getString(R.string.no_key_selected_dialog_text)) - .setPositiveButton(resources.getString(R.string.dialog_ok)) { _, _ -> - val intent = Intent(activity, UserPreference::class.java) - startActivity(intent) - } - .show() - return false - } return true } diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index f713ad26..98873fad 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -39,12 +39,10 @@ import com.github.ajalt.timberkt.Timber.tag import com.github.ajalt.timberkt.d import com.github.ajalt.timberkt.w import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity import com.zeapo.pwdstore.autofill.oreo.BrowserAutofillSupportLevel import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportLevel import com.zeapo.pwdstore.crypto.BasePgpActivity -import com.zeapo.pwdstore.crypto.GetKeyIdsActivity import com.zeapo.pwdstore.git.GitConfigActivity import com.zeapo.pwdstore.git.GitServerConfigActivity import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary @@ -57,8 +55,6 @@ import com.zeapo.pwdstore.utils.autofillManager import com.zeapo.pwdstore.utils.getEncryptedPrefs import java.io.File import java.io.IOException -import java.util.HashSet -import me.msfjarvis.openpgpktx.util.OpenPgpUtils typealias ClickListener = Preference.OnPreferenceClickListener typealias ChangeListener = Preference.OnPreferenceChangeListener @@ -106,9 +102,6 @@ class UserPreference : AppCompatActivity() { } } - // Crypto preferences - val keyPreference = findPreference<Preference>(PreferenceKeys.OPENPGP_KEY_ID_PREF) - // General preferences val showTimePreference = findPreference<Preference>(PreferenceKeys.GENERAL_SHOW_TIME) val clearClipboard20xPreference = findPreference<CheckBoxPreference>(PreferenceKeys.CLEAR_CLIPBOARD_20X) @@ -155,24 +148,6 @@ class UserPreference : AppCompatActivity() { appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}" - keyPreference?.let { pref -> - updateKeyIDsSummary(pref) - pref.onPreferenceClickListener = ClickListener { - val providerPackageName = requireNotNull(sharedPreferences.getString(PreferenceKeys.OPENPGP_PROVIDER_LIST, "")) - if (providerPackageName.isEmpty()) { - Snackbar.make(requireView(), resources.getString(R.string.provider_toast_text), Snackbar.LENGTH_LONG).show() - false - } else { - val intent = Intent(callingActivity, GetKeyIdsActivity::class.java) - val keySelectResult = registerForActivityResult(StartActivityForResult()) { - updateKeyIDsSummary(pref) - } - keySelectResult.launch(intent) - true - } - } - } - sshKeyPreference?.onPreferenceClickListener = ClickListener { callingActivity.getSshKey() true @@ -369,18 +344,6 @@ class UserPreference : AppCompatActivity() { } } - private fun updateKeyIDsSummary(preference: Preference) { - val selectedKeys = (sharedPreferences.getStringSet(PreferenceKeys.OPENPGP_KEY_IDS_SET, null) - ?: HashSet()).toTypedArray() - preference.summary = if (selectedKeys.isEmpty()) { - resources.getString(R.string.pref_no_key_selected) - } else { - selectedKeys.joinToString(separator = ";") { s -> - OpenPgpUtils.convertKeyIdToHex(s.toLong()) - } - } - } - private fun updateXkPasswdPrefsVisibility(newValue: Any?, prefIsCustomDict: CheckBoxPreference?, prefCustomDictPicker: Preference?) { when (newValue as String) { BasePgpActivity.KEY_PWGEN_TYPE_CLASSIC -> { diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt index eabd24c0..3656bc7e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt @@ -24,6 +24,7 @@ import com.zeapo.pwdstore.autofill.oreo.Credentials import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure import com.zeapo.pwdstore.autofill.oreo.FillableForm import com.zeapo.pwdstore.model.PasswordEntry +import com.zeapo.pwdstore.utils.OPENPGP_PROVIDER import java.io.ByteArrayOutputStream import java.io.File import java.io.FileNotFoundException @@ -53,7 +54,6 @@ class AutofillDecryptActivity : AppCompatActivity(), CoroutineScope { private const val EXTRA_FILE_PATH = "com.zeapo.pwdstore.autofill.oreo.EXTRA_FILE_PATH" private const val EXTRA_SEARCH_ACTION = "com.zeapo.pwdstore.autofill.oreo.EXTRA_SEARCH_ACTION" - private const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain" private var decryptFileRequestCode = 1 diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt index 39164e8f..ec8e7f9b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt @@ -14,8 +14,6 @@ import android.os.Build import android.os.Bundle import android.text.format.DateUtils import android.view.WindowManager -import android.widget.Toast -import androidx.activity.result.ActivityResultLauncher import androidx.annotation.CallSuper import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity @@ -26,7 +24,7 @@ import com.github.ajalt.timberkt.i import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.ClipboardService import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.UserPreference +import com.zeapo.pwdstore.utils.OPENPGP_PROVIDER import com.zeapo.pwdstore.utils.PreferenceKeys import com.zeapo.pwdstore.utils.clipboard import com.zeapo.pwdstore.utils.snackbar @@ -74,12 +72,6 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) } /** - * Read-only field for getting the list of OpenPGP key IDs that we have access to. - */ - var keyIDs = emptySet<String>() - private set - - /** * Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain. */ private var serviceConnection: OpenPgpServiceConnection? = null @@ -87,15 +79,13 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou /** * [onCreate] sets the window up with the right flags to prevent auth leaks through screenshots - * or recent apps screen and fills in [keyIDs] from [settings] + * or recent apps screen. */ @CallSuper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) tag(TAG) - - keyIDs = settings.getStringSet(PreferenceKeys.OPENPGP_KEY_IDS_SET, null) ?: emptySet() } /** @@ -128,20 +118,11 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou } /** - * Method for subclasses to initiate binding with [OpenPgpServiceConnection]. The design choices - * here are a bit dubious at first glance. We require passing a [ActivityResultLauncher] because - * it lets us react to having a OpenPgp provider selected without relying on the now deprecated - * [startActivityForResult]. + * Method for subclasses to initiate binding with [OpenPgpServiceConnection]. */ - fun bindToOpenKeychain(onBoundListener: OpenPgpServiceConnection.OnBound, activityResult: ActivityResultLauncher<Intent>) { - val providerPackageName = settings.getString(PreferenceKeys.OPENPGP_PROVIDER_LIST, "") - if (providerPackageName.isNullOrEmpty()) { - Toast.makeText(this, resources.getString(R.string.provider_toast_text), Toast.LENGTH_LONG).show() - activityResult.launch(Intent(this, UserPreference::class.java)) - } else { - serviceConnection = OpenPgpServiceConnection(this, providerPackageName, onBoundListener) - serviceConnection?.bindToService() - } + fun bindToOpenKeychain(onBoundListener: OpenPgpServiceConnection.OnBound) { + serviceConnection = OpenPgpServiceConnection(this, OPENPGP_PROVIDER, onBoundListener) + serviceConnection?.bindToService() } /** diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt index 3425a346..cc70dcc1 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt @@ -13,7 +13,6 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.activity.result.IntentSenderRequest -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult import androidx.lifecycle.lifecycleScope import com.github.ajalt.timberkt.e @@ -57,13 +56,9 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { } } - private val openKeychainResult = registerForActivityResult(StartActivityForResult()) { - decryptAndVerify() - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - bindToOpenKeychain(this, openKeychainResult) + bindToOpenKeychain(this) title = name with(binding) { setContentView(root) @@ -134,7 +129,7 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { @OptIn(ExperimentalTime::class) private fun decryptAndVerify(receivedIntent: Intent? = null) { if (api == null) { - bindToOpenKeychain(this, openKeychainResult) + bindToOpenKeychain(this) return } val data = receivedIntent ?: Intent() diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt deleted file mode 100644 index 97f6bab2..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/GetKeyIdsActivity.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.crypto - -import android.content.Intent -import android.os.Bundle -import androidx.activity.result.IntentSenderRequest -import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult -import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult -import androidx.core.content.edit -import androidx.lifecycle.lifecycleScope -import com.github.ajalt.timberkt.Timber -import com.github.ajalt.timberkt.e -import com.zeapo.pwdstore.utils.PreferenceKeys -import com.zeapo.pwdstore.utils.snackbar -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import me.msfjarvis.openpgpktx.util.OpenPgpApi -import org.openintents.openpgp.IOpenPgpService2 - -class GetKeyIdsActivity : BasePgpActivity() { - - private val getKeyIds = registerForActivityResult(StartActivityForResult()) { getKeyIds() } - - private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result -> - if (result.data == null) { - setResult(RESULT_CANCELED, null) - finish() - return@registerForActivityResult - } - - when (result.resultCode) { - RESULT_OK -> getKeyIds(result.data) - RESULT_CANCELED -> { - setResult(RESULT_CANCELED, result.data) - finish() - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - bindToOpenKeychain(this, getKeyIds) - } - - override fun onBound(service: IOpenPgpService2) { - super.onBound(service) - getKeyIds() - } - - override fun onError(e: Exception) { - e(e) - } - - /** - * Get the Key ids from OpenKeychain - */ - private fun getKeyIds(receivedIntent: Intent? = null) { - val data = receivedIntent ?: Intent() - data.action = OpenPgpApi.ACTION_GET_KEY_IDS - lifecycleScope.launch(Dispatchers.IO) { - api?.executeApiAsync(data, null, null) { result -> - when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { - OpenPgpApi.RESULT_CODE_SUCCESS -> { - try { - val ids = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS) - ?: LongArray(0) - val keys = ids.map { it.toString() }.toSet() - // use Long - settings.edit { putStringSet(PreferenceKeys.OPENPGP_KEY_IDS_SET, keys) } - snackbar(message = "PGP keys selected") - setResult(RESULT_OK) - finish() - } catch (e: Exception) { - Timber.e(e) { "An Exception occurred" } - } - } - 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/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt index 286f2951..6cacf4b9 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -11,7 +11,10 @@ import android.text.InputType import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult +import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.lifecycle.lifecycleScope @@ -40,6 +43,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import me.msfjarvis.openpgpktx.util.OpenPgpApi import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection +import me.msfjarvis.openpgpktx.util.OpenPgpUtils import org.eclipse.jgit.api.Git class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { @@ -51,13 +55,45 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB private val suggestedExtra by lazy { intent.getStringExtra(EXTRA_EXTRA_CONTENT) } private val shouldGeneratePassword by lazy { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) } private val editing by lazy { intent.getBooleanExtra(EXTRA_EDITING, false) } - private val doNothing = registerForActivityResult(StartActivityForResult()) {} - private var oldCategory: String? = null private val oldFileName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) } + private var oldCategory: String? = null + private var copy: Boolean = false + + private val userInteractionRequiredResult: ActivityResultLauncher<IntentSenderRequest> = 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 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) - bindToOpenKeychain(this, doNothing) + bindToOpenKeychain(this) title = if (editing) getString(R.string.edit_password) else @@ -172,8 +208,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB setResult(RESULT_CANCELED) finish() } - R.id.save_password -> encrypt() - R.id.save_and_copy_password -> encrypt(copy = true) + R.id.save_password -> { + copy = false + encrypt() + } + R.id.save_and_copy_password -> { + copy = true + encrypt() + } else -> return super.onOptionsItemSelected(item) } return true @@ -202,10 +244,42 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB otpImportButton.isVisible = !entry.hasTotp() } + private sealed class GpgIdentifier { + data class KeyId(val id: Long) : GpgIdentifier() + data class UserId(val email: String) : GpgIdentifier() + } + + @OptIn(ExperimentalUnsignedTypes::class) + private fun parseGpgIdentifier(identifier: String) : GpgIdentifier? { + // 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() + return GpgIdentifier.KeyId(maybeLongKeyId.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 GpgIdentifier.KeyId(keyId.toLong()) + } + + return OpenPgpUtils.splitUserId(identifier).email?.let { GpgIdentifier.UserId(it) } + } + /** * Encrypts the password and the extra content */ - private fun encrypt(copy: Boolean = false) = with(binding) { + private fun encrypt(receivedIntent: Intent? = null) = with(binding) { val editName = filename.text.toString().trim() val editPass = password.text.toString() val editExtra = extraContent.text.toString() @@ -227,12 +301,25 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB copyPasswordToClipboard(editPass) } - val data = Intent() + val data = receivedIntent ?: Intent() data.action = OpenPgpApi.ACTION_ENCRYPT - // EXTRA_KEY_IDS requires long[] - val longKeys = keyIDs.map { it.toLong() } - data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, longKeys.toLongArray()) + // pass enters the key ID into `.gpg-id`. + val repoRoot = PasswordRepository.getRepositoryDirectory(applicationContext) + val gpgIdentifierFile = File(repoRoot, directory.text.toString()).findTillRoot(".gpg-id", repoRoot) + if (gpgIdentifierFile == null) { + snackbar(message = resources.getString(R.string.failed_to_find_key_id)) + return@with + } + val gpgIdentifierFileContent = gpgIdentifierFile.useLines { it.firstOrNull() } ?: "" + when (val identifier = parseGpgIdentifier(gpgIdentifierFileContent)) { + is GpgIdentifier.KeyId -> data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, arrayOf(identifier.id)) + is GpgIdentifier.UserId -> data.putExtra(OpenPgpApi.EXTRA_USER_IDS, arrayOf(identifier.email)) + null -> { + snackbar(message = resources.getString(R.string.invalid_gpg_id)) + return@with + } + } data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true) val content = "$editPass\n$editExtra" @@ -347,6 +434,10 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB e(e) { "An Exception occurred" } } } + 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/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt index 19b651b7..b274b47d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt @@ -29,6 +29,8 @@ import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepositoryDirect import java.io.File import org.eclipse.jgit.api.Git +const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain" + fun Int.clearFlag(flag: Int): Int { return this and flag.inv() } |