From 5041c0e6fccd521b3fac83b6c832bbc8f13dad23 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 16:08:59 +0530 Subject: [PATCH 01/11] Allow importing TOTP configuration through QR codes Signed-off-by: Harsh Shandilya --- CHANGELOG.md | 1 + app/build.gradle | 4 +++- .../crypto/PasswordCreationActivity.kt | 19 +++++++++++++++++++ .../res/layout/password_creation_activity.xml | 11 ++++++++++- app/src/main/res/values/strings.xml | 3 +++ dependencies.gradle | 6 +++++- 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adcdc57f7..9c9aafdfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. - TOTP support is reintroduced by popular demand. HOTP continues to be unsupported and heavily discouraged. - Initial support for detecting and filling OTP fields with Autofill +- Importing TOTP secrets using URI codes ## [1.9.1] - 2020-06-28 diff --git a/app/build.gradle b/app/build.gradle index 70b1f1f78..eaef71c27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,6 +93,9 @@ dependencies { implementation deps.kotlin.coroutines.android implementation deps.kotlin.coroutines.core + implementation deps.first_party.openpgp_ktx + implementation deps.first_party.zxing_android_embedded + implementation deps.third_party.commons_codec implementation deps.third_party.fastscroll implementation(deps.third_party.jgit) { @@ -102,7 +105,6 @@ dependencies { implementation deps.third_party.sshj implementation deps.third_party.bouncycastle implementation deps.third_party.plumber - implementation deps.third_party.openpgp_ktx implementation deps.third_party.ssh_auth implementation deps.third_party.timber implementation deps.third_party.timberkt 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 81a739882..095d3f3a0 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -17,6 +17,8 @@ import androidx.core.widget.doOnTextChanged import androidx.lifecycle.lifecycleScope import com.github.ajalt.timberkt.e import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.zxing.integration.android.IntentIntegrator +import com.google.zxing.integration.android.IntentIntegrator.QR_CODE import com.zeapo.pwdstore.R import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure @@ -62,6 +64,23 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB with(binding) { setContentView(root) generatePassword.setOnClickListener { generatePassword() } + otpImportButton.setOnClickListener { + registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) + extraContent.append("\n${intentResult.contents}") + snackbar(message = getString(R.string.otp_import_success)) + } else { + snackbar(message = getString(R.string.otp_import_failure)) + } + }.launch( + IntentIntegrator(this@PasswordCreationActivity) + .setOrientationLocked(false) + .setBeepEnabled(false) + .setDesiredBarcodeFormats(QR_CODE) + .createScanIntent() + ) + } category.apply { if (suggestedName != null || suggestedPass != null || shouldGeneratePassword) { diff --git a/app/src/main/res/layout/password_creation_activity.xml b/app/src/main/res/layout/password_creation_activity.xml index 13af597c1..60aa5ff9a 100644 --- a/app/src/main/res/layout/password_creation_activity.xml +++ b/app/src/main/res/layout/password_creation_activity.xml @@ -84,6 +84,15 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2f04b3887..bce91100d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -383,4 +383,7 @@ Failed to write password file to the store, please try again. Failed to delete password file %1$s from the store, please delete it manually. File already exists, please use a different name + Setup OTP from QR code + Successfully imported TOTP configuration + Failed to import TOTP configuration diff --git a/dependencies.gradle b/dependencies.gradle index 0246f5689..f5c7c965e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -44,6 +44,11 @@ ext.deps = [ swiperefreshlayout: 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' ], + first_party: [ + openpgp_ktx: 'com.github.android-password-store:openpgp-ktx:2.0.0', + zxing_android_embedded: 'com.github.android-password-store:zxing-android-embedded:v4.1.0-aps' + ], + third_party: [ bouncycastle: 'org.bouncycastle:bcprov-jdk15on:1.65.01', commons_codec: 'commons-codec:commons-codec:1.13', @@ -52,7 +57,6 @@ ext.deps = [ jgit: 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r', leakcanary: 'com.squareup.leakcanary:leakcanary-android:2.4', plumber: 'com.squareup.leakcanary:plumber-android:2.4', - openpgp_ktx: 'com.github.android-password-store:openpgp-ktx:2.0.0', sshj: 'com.hierynomus:sshj:0.29.0', ssh_auth: 'org.sufficientlysecure:sshauthentication-api:1.0', timber: 'com.jakewharton.timber:timber:4.7.1', From e413b849b001c2a21712b13090f9132969f4fdfd Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 16:49:58 +0530 Subject: [PATCH 02/11] Hide OTP import button if TOTP is already present Signed-off-by: Harsh Shandilya --- .../java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 2 ++ 1 file changed, 2 insertions(+) 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 095d3f3a0..319d08c50 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -64,6 +64,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB with(binding) { setContentView(root) generatePassword.setOnClickListener { generatePassword() } + otpImportButton.isVisible = !(suggestedExtra != null && + (suggestedExtra.contains("otpauth://") || suggestedExtra.contains("totp:"))) otpImportButton.setOnClickListener { registerForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { From 4af361770879750bd33b79a1c03a3637e4ff31da Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 17:02:12 +0530 Subject: [PATCH 03/11] Convoluted newline nonsense Signed-off-by: Harsh Shandilya --- .../com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 319d08c50..decbea6b9 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -70,7 +70,10 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB registerForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) - extraContent.append("\n${intentResult.contents}") + if (extraContent.text.toString().last() != '\n') + extraContent.append("\n${intentResult.contents}\n") + else + extraContent.append("${intentResult.contents}\n") snackbar(message = getString(R.string.otp_import_success)) } else { snackbar(message = getString(R.string.otp_import_failure)) From 5c1427498057206d5a064555b9bd2e88d9a86d58 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 17:04:00 +0530 Subject: [PATCH 04/11] Silence warning Signed-off-by: Harsh Shandilya --- .../java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 decbea6b9..e75214441 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -119,7 +119,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val username = filename.text.toString() val extras = "username:$username\n${extraContent.text}" - filename.setText("") + filename.text?.clear() extraContent.setText(extras) } else { // User wants to disable username encryption, so we extract the From 7acfce0a4b0c2edeea9d69f9246d78aa938f42d6 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 17:08:52 +0530 Subject: [PATCH 05/11] Try simplifying some things Signed-off-by: Harsh Shandilya --- .../com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e75214441..34eaec07a 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -63,14 +63,14 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB getString(R.string.new_password_title) with(binding) { setContentView(root) + val pwEntry = PasswordEntry("${suggestedPass}\n${suggestedExtra}") generatePassword.setOnClickListener { generatePassword() } - otpImportButton.isVisible = !(suggestedExtra != null && - (suggestedExtra.contains("otpauth://") || suggestedExtra.contains("totp:"))) + otpImportButton.isVisible = !pwEntry.hasTotp() otpImportButton.setOnClickListener { registerForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) - if (extraContent.text.toString().last() != '\n') + if (pwEntry.extraContent.last() != '\n') extraContent.append("\n${intentResult.contents}\n") else extraContent.append("${intentResult.contents}\n") From 6349328bfbd505dafa48bc48c8a99e4429d8552b Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 18:03:16 +0530 Subject: [PATCH 06/11] Reuse existing logic Signed-off-by: Harsh Shandilya --- .../crypto/PasswordCreationActivity.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 34eaec07a..010fb8443 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -63,14 +63,13 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB getString(R.string.new_password_title) with(binding) { setContentView(root) - val pwEntry = PasswordEntry("${suggestedPass}\n${suggestedExtra}") generatePassword.setOnClickListener { generatePassword() } - otpImportButton.isVisible = !pwEntry.hasTotp() otpImportButton.setOnClickListener { registerForActivityResult(StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { + otpImportButton.isVisible = false val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) - if (pwEntry.extraContent.last() != '\n') + if (extraContent.text.toString().last() != '\n') extraContent.append("\n${intentResult.contents}\n") else extraContent.append("${intentResult.contents}\n") @@ -128,20 +127,20 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val username = entry.username // username should not be null here by the logic in - // updateEncryptUsernameState, but it could still happen due to + // updateViewState, but it could still happen due to // input lag. if (username != null) { filename.setText(username) extraContent.setText(entry.extraContentWithoutAuthData) } } - updateEncryptUsernameState() + updateViewState() } } listOf(filename, extraContent).forEach { - it.doOnTextChanged { _, _, _, _ -> updateEncryptUsernameState() } + it.doOnTextChanged { _, _, _, _ -> updateViewState() } } - updateEncryptUsernameState() + updateViewState() } suggestedPass?.let { password.setText(it) @@ -182,17 +181,18 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } - private fun updateEncryptUsernameState() = with(binding) { + private fun updateViewState() = with(binding) { + // Use PasswordEntry to parse extras for username + val entry = PasswordEntry("PLACEHOLDER\n${extraContent.text}") encryptUsername.apply { if (visibility != View.VISIBLE) return@with val hasUsernameInFileName = filename.text.toString().isNotBlank() - // Use PasswordEntry to parse extras for username - val entry = PasswordEntry("PLACEHOLDER\n${extraContent.text}") val hasUsernameInExtras = entry.hasUsername() isEnabled = hasUsernameInFileName xor hasUsernameInExtras isChecked = hasUsernameInExtras } + otpImportButton.isVisible = !entry.hasTotp() } /** From b88dc90236e904ffa0c18ebe9ded37bbc66d26e1 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 18:10:18 +0530 Subject: [PATCH 07/11] Improve OTP import button UI Signed-off-by: Harsh Shandilya --- app/src/main/res/drawable/ic_qr_code_scanner.xml | 9 +++++++++ app/src/main/res/layout/password_creation_activity.xml | 4 +++- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/ic_qr_code_scanner.xml diff --git a/app/src/main/res/drawable/ic_qr_code_scanner.xml b/app/src/main/res/drawable/ic_qr_code_scanner.xml new file mode 100644 index 000000000..45a618ac2 --- /dev/null +++ b/app/src/main/res/drawable/ic_qr_code_scanner.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/password_creation_activity.xml b/app/src/main/res/layout/password_creation_activity.xml index 60aa5ff9a..e0b25786e 100644 --- a/app/src/main/res/layout/password_creation_activity.xml +++ b/app/src/main/res/layout/password_creation_activity.xml @@ -89,7 +89,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="8dp" - android:text="@string/setup_otp_from_qr_code" + android:text="@string/add_otp" + app:icon="@drawable/ic_qr_code_scanner" + app:iconTint="?attr/colorOnSecondary" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/extra_input_layout" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bce91100d..3023d9957 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -383,7 +383,7 @@ Failed to write password file to the store, please try again. Failed to delete password file %1$s from the store, please delete it manually. File already exists, please use a different name - Setup OTP from QR code + Add OTP Successfully imported TOTP configuration Failed to import TOTP configuration From ef1bd05df151a8baefa5d2940eef5791b95794bc Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 18:15:12 +0530 Subject: [PATCH 08/11] fix changelog brainfart Signed-off-by: Harsh Shandilya --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9aafdfb..a10857c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ All notable changes to this project will be documented in this file. - TOTP support is reintroduced by popular demand. HOTP continues to be unsupported and heavily discouraged. - Initial support for detecting and filling OTP fields with Autofill -- Importing TOTP secrets using URI codes +- Importing TOTP secrets using QR codes ## [1.9.1] - 2020-06-28 From 975efd15aa1617ee0feb41f0459afcc68827b050 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 18:17:57 +0530 Subject: [PATCH 09/11] Support directly importing secrets Is this a thing? Signed-off-by: Harsh Shandilya --- .../zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 010fb8443..6ea5d1e48 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -69,10 +69,15 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB if (result.resultCode == RESULT_OK) { otpImportButton.isVisible = false val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) + val contents = if (intentResult.contents.startsWith("otpauth://")) { + "${intentResult.contents}\n" + } else { + "totp: ${intentResult.contents}\n" + } if (extraContent.text.toString().last() != '\n') - extraContent.append("\n${intentResult.contents}\n") + extraContent.append("\n$contents") else - extraContent.append("${intentResult.contents}\n") + extraContent.append(contents) snackbar(message = getString(R.string.otp_import_success)) } else { snackbar(message = getString(R.string.otp_import_failure)) From 66a4c4bf2dd1204e1d5302ee6bc66f2c211b241f Mon Sep 17 00:00:00 2001 From: Fabian Henneke Date: Tue, 30 Jun 2020 14:50:55 +0200 Subject: [PATCH 10/11] Fix a crash if extra contents are empty --- .../java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 6ea5d1e48..a9b1219d3 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -74,7 +74,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } else { "totp: ${intentResult.contents}\n" } - if (extraContent.text.toString().last() != '\n') + val currentExtras = extraContent.text.toString() + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') extraContent.append("\n$contents") else extraContent.append(contents) From 85560e7e6eb53d0d7d4ae4a743cd1f3e8f2ec36b Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Tue, 30 Jun 2020 18:29:13 +0530 Subject: [PATCH 11/11] Force CaptureActivity to respect device orientation Signed-off-by: Harsh Shandilya --- app/src/main/AndroidManifest.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b0c3193dc..2098abc98 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,14 @@ + +