diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2022-08-24 22:44:02 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-24 17:14:02 +0000 |
commit | 8129495608c1029cdb09071a80a73971d20f25d7 (patch) | |
tree | d2865f94ff382b045c0a5bb3016391b45e43fdf6 /app | |
parent | 3178ec97632c5e9655cf3fc7de9fa1d2b4f30243 (diff) |
Implement support for `.gpg-id` (#2080)
Diffstat (limited to 'app')
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> |