aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2022-08-24 22:44:02 +0530
committerGitHub <noreply@github.com>2022-08-24 17:14:02 +0000
commit8129495608c1029cdb09071a80a73971d20f25d7 (patch)
treed2865f94ff382b045c0a5bb3016391b45e43fdf6 /app
parent3178ec97632c5e9655cf3fc7de9fa1d2b4f30243 (diff)
Implement support for `.gpg-id` (#2080)
Diffstat (limited to 'app')
-rw-r--r--app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt21
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt8
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt47
-rw-r--r--app/src/main/res/values/strings.xml1
4 files changed, 68 insertions, 9 deletions
diff --git a/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt b/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
index 6c214fd1..297ada95 100644
--- a/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
+++ b/app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
@@ -5,9 +5,10 @@
package app.passwordstore.data.crypto
+import app.passwordstore.crypto.GpgIdentifier
import app.passwordstore.crypto.PGPKeyManager
import app.passwordstore.crypto.PGPainlessCryptoHandler
-import app.passwordstore.util.extensions.isOk
+import com.github.michaelbull.result.getAll
import com.github.michaelbull.result.unwrap
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@@ -30,8 +31,12 @@ constructor(
withContext(Dispatchers.IO) { decryptPgp(password, message, out) }
}
- suspend fun encrypt(content: ByteArrayInputStream, out: ByteArrayOutputStream) {
- withContext(Dispatchers.IO) { encryptPgp(content, out) }
+ suspend fun encrypt(
+ identities: List<GpgIdentifier>,
+ content: ByteArrayInputStream,
+ out: ByteArrayOutputStream,
+ ) {
+ withContext(Dispatchers.IO) { encryptPgp(identities, content, out) }
}
private suspend fun decryptPgp(
@@ -41,11 +46,15 @@ constructor(
) {
val keys = pgpKeyManager.getAllKeys().unwrap()
// Iterates through the keys until the first successful decryption, then returns.
- keys.firstOrNull { key -> pgpCryptoHandler.decrypt(key, password, message, out).isOk() }
+ pgpCryptoHandler.decrypt(keys, password, message, out)
}
- private suspend fun encryptPgp(content: ByteArrayInputStream, out: ByteArrayOutputStream) {
- val keys = pgpKeyManager.getAllKeys().unwrap()
+ private suspend fun encryptPgp(
+ identities: List<GpgIdentifier>,
+ content: ByteArrayInputStream,
+ out: ByteArrayOutputStream,
+ ) {
+ val keys = identities.map { ident -> pgpKeyManager.getKeyById(ident) }.getAll()
pgpCryptoHandler.encrypt(
keys,
content,
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
index 3fb0d52f..fa37f501 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/DecryptActivity.kt
@@ -21,6 +21,7 @@ import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.extensions.viewBinding
import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.runCatching
+import com.github.michaelbull.result.unwrapError
import dagger.hilt.android.AndroidEntryPoint
import java.io.ByteArrayOutputStream
import java.io.File
@@ -35,6 +36,8 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import logcat.LogPriority.ERROR
+import logcat.logcat
@OptIn(ExperimentalTime::class)
@AndroidEntryPoint
@@ -140,7 +143,9 @@ class DecryptActivity : BasePgpActivity() {
lifecycleScope.launch(Dispatchers.Main) {
dialog.password.collectLatest { value ->
if (value != null) {
- if (runCatching { decrypt(value) }.isErr()) {
+ val res = runCatching { decrypt(value) }
+ if (res.isErr()) {
+ logcat(ERROR) { res.unwrapError().stackTraceToString() }
decrypt(isError = true)
}
}
@@ -161,7 +166,6 @@ class DecryptActivity : BasePgpActivity() {
)
outputStream
}
- require(result.size() != 0) { "Incorrect password" }
startAutoDismissTimer()
val entry = passwordEntryFactory.create(result.toByteArray())
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
index 2e3e48a9..fddc2943 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/PasswordCreationActivity.kt
@@ -24,8 +24,10 @@ import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
+import app.passwordstore.crypto.GpgIdentifier
import app.passwordstore.data.crypto.CryptoRepository
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
@@ -332,6 +334,32 @@ class PasswordCreationActivity : BasePgpActivity() {
copyPasswordToClipboard(editPass)
}
+ // 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()).not()) {
+ snackbar(message = resources.getString(R.string.invalid_gpg_id))
+ }
+ return@with
+ }
+ }
+ .filterIsInstance<GpgIdentifier>()
+ if (gpgIdentifiers.isEmpty()) {
+ error("Failed to parse identifiers from .gpg-id")
+ }
val content = "$editPass\n$editExtra"
val path =
when {
@@ -360,7 +388,7 @@ class PasswordCreationActivity : BasePgpActivity() {
val result =
withContext(Dispatchers.IO) {
val outputStream = ByteArrayOutputStream()
- repository.encrypt(content.byteInputStream(), outputStream)
+ repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream)
outputStream
}
val file = File(path)
@@ -457,6 +485,23 @@ class PasswordCreationActivity : BasePgpActivity() {
}
}
+ @Suppress("ReturnCount")
+ 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
+ }
+ }
+
companion object {
private const val KEY_PWGEN_TYPE_CLASSIC = "classic"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d2c9ab06..9997f6aa 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -299,6 +299,7 @@
<string name="otp_import_success">Successfully imported TOTP configuration</string>
<string name="otp_import_failure">Failed to import TOTP configuration</string>
<string name="exporting_passwords">Exporting passwords…</string>
+ <string name="invalid_gpg_id">Found .gpg-id, but it contains an invalid key ID, fingerprint or user ID</string>
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
<string name="directory_hint">Directory</string>
<string name="new_folder_set_gpg_key">Set GPG key for directory</string>