From e7463ec24c929860f0a7311b558f496919d54d20 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 28 May 2020 22:42:13 +0530 Subject: Remove HOTP/TOTP support (#806) --- app/build.gradle | 3 + .../java/com/zeapo/pwdstore/PasswordEntryTest.kt | 104 -------------- .../main/java/com/zeapo/pwdstore/PasswordEntry.kt | 104 +------------- .../main/java/com/zeapo/pwdstore/PasswordStore.kt | 6 +- .../main/java/com/zeapo/pwdstore/UserPreference.kt | 8 -- .../java/com/zeapo/pwdstore/crypto/PgpActivity.kt | 158 +-------------------- .../main/java/com/zeapo/pwdstore/utils/Otp.java | 63 -------- app/src/main/res/layout/decrypt_layout.xml | 43 +----- app/src/main/res/layout/otp_confirm_layout.xml | 23 --- app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 10 -- app/src/main/res/values-fr/strings.xml | 10 -- app/src/main/res/values-ru/strings.xml | 10 -- app/src/main/res/values/strings.xml | 11 -- app/src/main/res/xml/preference.xml | 3 - .../java/com/zeapo/pwdstore/PasswordEntryTest.kt | 63 ++++++++ 17 files changed, 75 insertions(+), 547 deletions(-) delete mode 100644 app/src/androidTest/java/com/zeapo/pwdstore/PasswordEntryTest.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/Otp.java delete mode 100644 app/src/main/res/layout/otp_confirm_layout.xml create mode 100644 app/src/test/java/com/zeapo/pwdstore/PasswordEntryTest.kt (limited to 'app') diff --git a/app/build.gradle b/app/build.gradle index 2e0671a7..2e203705 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,4 +123,7 @@ dependencies { androidTestImplementation deps.testing.androidx.junit androidTestImplementation deps.testing.androidx.espresso_core androidTestImplementation deps.testing.androidx.espresso_intents + + testImplementation deps.testing.junit + testImplementation deps.testing.kotlin_test_junit } diff --git a/app/src/androidTest/java/com/zeapo/pwdstore/PasswordEntryTest.kt b/app/src/androidTest/java/com/zeapo/pwdstore/PasswordEntryTest.kt deleted file mode 100644 index 5feb5aaf..00000000 --- a/app/src/androidTest/java/com/zeapo/pwdstore/PasswordEntryTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore - -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class PasswordEntryTest { - @Test fun testGetPassword() { - assertEquals("fooooo", PasswordEntry("fooooo\nbla\n").password) - assertEquals("fooooo", PasswordEntry("fooooo\nbla").password) - assertEquals("fooooo", PasswordEntry("fooooo\n").password) - assertEquals("fooooo", PasswordEntry("fooooo").password) - assertEquals("", PasswordEntry("\nblubb\n").password) - assertEquals("", PasswordEntry("\nblubb").password) - assertEquals("", PasswordEntry("\n").password) - assertEquals("", PasswordEntry("").password) - } - - @Test fun testGetExtraContent() { - assertEquals("bla\n", PasswordEntry("fooooo\nbla\n").extraContent) - assertEquals("bla", PasswordEntry("fooooo\nbla").extraContent) - assertEquals("", PasswordEntry("fooooo\n").extraContent) - assertEquals("", PasswordEntry("fooooo").extraContent) - assertEquals("blubb\n", PasswordEntry("\nblubb\n").extraContent) - assertEquals("blubb", PasswordEntry("\nblubb").extraContent) - assertEquals("", PasswordEntry("\n").extraContent) - assertEquals("", PasswordEntry("").extraContent) - } - - @Test fun testGetUsername() { - for (field in PasswordEntry.USERNAME_FIELDS) { - assertEquals("username", PasswordEntry("\n$field username").username) - assertEquals("username", PasswordEntry("\n${field.toUpperCase()} username").username) - } - assertEquals( - "username", - PasswordEntry("secret\nextra\nlogin: username\ncontent\n").username) - assertEquals( - "username", - PasswordEntry("\nextra\nusername: username\ncontent\n").username) - assertEquals( - "username", PasswordEntry("\nUSERNaMe: username\ncontent\n").username) - assertEquals("username", PasswordEntry("\nlogin: username").username) - assertEquals("foo@example.com", PasswordEntry("\nemail: foo@example.com").username) - assertEquals("username", PasswordEntry("\nidentity: username\nlogin: another_username").username) - assertEquals("username", PasswordEntry("\nLOGiN:username").username) - assertNull(PasswordEntry("secret\nextra\ncontent\n").username) - } - - @Test fun testHasUsername() { - assertTrue(PasswordEntry("secret\nextra\nlogin: username\ncontent\n").hasUsername()) - assertFalse(PasswordEntry("secret\nextra\ncontent\n").hasUsername()) - assertFalse(PasswordEntry("secret\nlogin failed\n").hasUsername()) - assertFalse(PasswordEntry("\n").hasUsername()) - assertFalse(PasswordEntry("").hasUsername()) - } - - @Test fun testNoTotpUriPresent() { - val entry = PasswordEntry("secret\nextra\nlogin: username\ncontent") - assertFalse(entry.hasTotp()) - assertNull(entry.totpSecret) - } - - @Test fun testTotpUriInPassword() { - val entry = PasswordEntry("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP") - assertTrue(entry.hasTotp()) - assertEquals("JBSWY3DPEHPK3PXP", entry.totpSecret) - } - - @Test fun testTotpUriInContent() { - val entry = PasswordEntry( - "secret\nusername: test\notpauth://totp/test?secret=JBSWY3DPEHPK3PXP") - assertTrue(entry.hasTotp()) - assertEquals("JBSWY3DPEHPK3PXP", entry.totpSecret) - } - - @Test fun testNoHotpUriPresent() { - val entry = PasswordEntry("secret\nextra\nlogin: username\ncontent") - assertFalse(entry.hasHotp()) - assertNull(entry.hotpSecret) - assertNull(entry.hotpCounter) - } - - @Test fun testHotpUriInPassword() { - val entry = PasswordEntry("otpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25") - assertTrue(entry.hasHotp()) - assertEquals("JBSWY3DPEHPK3PXP", entry.hotpSecret) - assertEquals(25, entry.hotpCounter) - } - - @Test fun testHotpUriInContent() { - val entry = PasswordEntry( - "secret\nusername: test\notpauth://hotp/test?secret=JBSWY3DPEHPK3PXP&counter=25") - assertTrue(entry.hasHotp()) - assertEquals("JBSWY3DPEHPK3PXP", entry.hotpSecret) - assertEquals(25, entry.hotpCounter) - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt index 32f671be..d9168d39 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt @@ -4,26 +4,18 @@ */ package com.zeapo.pwdstore -import android.net.Uri import java.io.ByteArrayOutputStream import java.io.UnsupportedEncodingException /** * A single entry in password store. */ -class PasswordEntry(private val content: String) { +class PasswordEntry(content: String) { val password: String val username: String? - val digits: String - val totpSecret: String? - val totpPeriod: Long - val totpAlgorithm: String - val hotpSecret: String? - val hotpCounter: Long? var extraContent: String private set - private var isIncremented = false @Throws(UnsupportedEncodingException::class) constructor(os: ByteArrayOutputStream) : this(os.toString("UTF-8")) @@ -31,12 +23,6 @@ class PasswordEntry(private val content: String) { init { val passContent = content.split("\n".toRegex(), 2).toTypedArray() password = passContent[0] - digits = findOtpDigits(content) - totpSecret = findTotpSecret(content) - totpPeriod = findTotpPeriod(content) - totpAlgorithm = findTotpAlgorithm(content) - hotpSecret = findHotpSecret(content) - hotpCounter = findHotpCounter(content) extraContent = findExtraContent(passContent) username = findUsername() } @@ -49,27 +35,6 @@ class PasswordEntry(private val content: String) { return username != null } - fun hasTotp(): Boolean { - return totpSecret != null - } - - fun hasHotp(): Boolean { - return hotpSecret != null && hotpCounter != null - } - - fun hotpIsIncremented(): Boolean { - return isIncremented - } - - fun incrementHotp() { - content.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://hotp/")) { - extraContent = extraContent.replaceFirst("counter=[0-9]+".toRegex(), "counter=${hotpCounter!! + 1}") - isIncremented = true - } - } - } - val extraContentWithoutUsername by lazy { var usernameFound = false extraContent.splitToSequence("\n").filter { line -> @@ -93,73 +58,8 @@ class PasswordEntry(private val content: String) { return null } - private fun findTotpSecret(decryptedContent: String): String? { - decryptedContent.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://totp/")) { - return Uri.parse(line).getQueryParameter("secret") - } - if (line.startsWith("totp:", ignoreCase = true)) { - return line.split(": *".toRegex(), 2).toTypedArray()[1] - } - } - return null - } - - private fun findOtpDigits(decryptedContent: String): String { - decryptedContent.split("\n".toRegex()).forEach { line -> - if ((line.startsWith("otpauth://totp/") || - line.startsWith("otpauth://hotp/")) && - Uri.parse(line).getQueryParameter("digits") != null) { - return Uri.parse(line).getQueryParameter("digits")!! - } - } - return "6" - } - - private fun findTotpPeriod(decryptedContent: String): Long { - decryptedContent.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://totp/") && - Uri.parse(line).getQueryParameter("period") != null) { - return java.lang.Long.parseLong(Uri.parse(line).getQueryParameter("period")!!) - } - } - return 30 - } - - private fun findTotpAlgorithm(decryptedContent: String): String { - decryptedContent.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://totp/") && - Uri.parse(line).getQueryParameter("algorithm") != null) { - return Uri.parse(line).getQueryParameter("algorithm")!! - } - } - return "sha1" - } - - private fun findHotpSecret(decryptedContent: String): String? { - decryptedContent.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://hotp/")) { - return Uri.parse(line).getQueryParameter("secret") - } - } - return null - } - - private fun findHotpCounter(decryptedContent: String): Long? { - decryptedContent.split("\n".toRegex()).forEach { line -> - if (line.startsWith("otpauth://hotp/")) { - return java.lang.Long.parseLong(Uri.parse(line).getQueryParameter("counter")!!) - } - } - return null - } - private fun findExtraContent(passContent: Array): String { - val extraContent = if (passContent.size > 1) passContent[1] else "" - // if there is a HOTP URI, we must return the extra content with the counter incremented - return if (hasHotp()) { - extraContent.replaceFirst("counter=[0-9]+".toRegex(), "counter=" + (hotpCounter!!).toString()) - } else extraContent + return if (passContent.size > 1) passContent[1] else "" } companion object { diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index 5857af74..45269145 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -636,16 +636,12 @@ class PasswordStore : AppCompatActivity() { when (requestCode) { // if we get here with a RESULT_OK then it's probably OK :) BaseGitActivity.REQUEST_CLONE -> settings.edit { putBoolean("repository_initialized", true) } - // if went from decrypt->edit and user saved changes or HOTP counter was - // incremented, we need to commitChange + // if went from decrypt->edit and user saved changes, we need to commitChange REQUEST_CODE_DECRYPT_AND_VERIFY -> { if (data != null && data.getBooleanExtra("needCommit", false)) { if (data.getStringExtra("OPERATION") == "EDIT") { commitChange(resources.getString(R.string.git_commit_edit_text, data.extras!!.getString("LONG_NAME"))) - } else { - commitChange(resources.getString(R.string.git_commit_increment_text, - data.extras!!.getString("LONG_NAME"))) } } refreshPasswordList() diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 10665e38..41404716 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -91,7 +91,6 @@ class UserPreference : AppCompatActivity() { val sshKeyPreference = findPreference("ssh_key") val sshKeygenPreference = findPreference("ssh_keygen") clearSavedPassPreference = findPreference("clear_saved_pass") - val clearHotpIncrementPreference = findPreference("hotp_remember_clear_choice") val viewSshKeyPreference = findPreference("ssh_see_key") val deleteRepoPreference = findPreference("git_delete_repo") val externalGitRepositoryPreference = findPreference("git_external") @@ -138,7 +137,6 @@ class UserPreference : AppCompatActivity() { selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected)) viewSshKeyPreference?.isVisible = sharedPreferences.getBoolean("use_generated_key", false) deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean("git_external", false) - clearHotpIncrementPreference?.isVisible = sharedPreferences.getBoolean("hotp_remember_check", false) clearClipboard20xPreference?.isVisible = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0 val selectedKeys = (sharedPreferences.getStringSet("openpgp_key_ids_set", null) ?: HashSet()).toTypedArray() @@ -197,12 +195,6 @@ class UserPreference : AppCompatActivity() { true } - clearHotpIncrementPreference?.onPreferenceClickListener = ClickListener { - sharedPreferences.edit { putBoolean("hotp_remember_check", false) } - it.isVisible = false - true - } - openkeystoreIdPreference?.onPreferenceClickListener = ClickListener { sharedPreferences.edit { putString("ssh_openkeystore_keyid", null) } it.isVisible = false diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt index 89399031..d9027230 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt @@ -21,14 +21,12 @@ import android.text.InputType import android.text.TextUtils import android.text.format.DateUtils import android.text.method.PasswordTransformationMethod -import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View import android.view.WindowManager import android.widget.Button -import android.widget.CheckBox import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout @@ -41,7 +39,6 @@ import androidx.preference.PreferenceManager import com.github.ajalt.timberkt.Timber.e import com.github.ajalt.timberkt.Timber.i import com.github.ajalt.timberkt.Timber.tag -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.ClipboardService import com.zeapo.pwdstore.PasswordEntry @@ -51,15 +48,11 @@ import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment -import com.zeapo.pwdstore.utils.Otp import kotlinx.android.synthetic.main.decrypt_layout.crypto_container_decrypt -import kotlinx.android.synthetic.main.decrypt_layout.crypto_copy_otp import kotlinx.android.synthetic.main.decrypt_layout.crypto_copy_username import kotlinx.android.synthetic.main.decrypt_layout.crypto_extra_show import kotlinx.android.synthetic.main.decrypt_layout.crypto_extra_show_layout import kotlinx.android.synthetic.main.decrypt_layout.crypto_extra_toggle_show -import kotlinx.android.synthetic.main.decrypt_layout.crypto_otp_show -import kotlinx.android.synthetic.main.decrypt_layout.crypto_otp_show_label import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_category_decrypt import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_file import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_last_changed @@ -93,7 +86,6 @@ import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.nio.charset.Charset -import java.util.Date class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { private val clipboard by lazy { getSystemService() } @@ -284,7 +276,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { } override fun onDestroy() { - checkAndIncrementHotp() super.onDestroy() mServiceConnection?.unbindFromService() } @@ -304,23 +295,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - if (passwordEntry?.hotpIsIncremented() == false) { - setResult(RESULT_CANCELED) - } - finish() - } + R.id.crypto_cancel_add, android.R.id.home -> finish() R.id.copy_password -> copyPasswordToClipBoard() R.id.share_password_as_plaintext -> shareAsPlaintext() R.id.edit_password -> editPassword() R.id.crypto_confirm_add -> encrypt() R.id.crypto_confirm_add_and_copy -> encrypt(true) - R.id.crypto_cancel_add -> { - if (passwordEntry?.hotpIsIncremented() == false) { - setResult(RESULT_CANCELED) - } - finish() - } else -> return super.onOptionsItemSelected(item) } return true @@ -463,84 +443,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { } } - if (entry.hasTotp() || entry.hasHotp()) { - crypto_extra_show_layout.visibility = View.VISIBLE - crypto_extra_show.typeface = monoTypeface - crypto_extra_show.text = entry.extraContent - - crypto_otp_show.visibility = View.VISIBLE - crypto_otp_show_label.visibility = View.VISIBLE - crypto_copy_otp.visibility = View.VISIBLE - - if (entry.hasTotp()) { - crypto_copy_otp.setOnClickListener { - copyOtpToClipBoard( - Otp.calculateCode( - entry.totpSecret, - Date().time / (1000 * entry.totpPeriod), - entry.totpAlgorithm, - entry.digits) - ) - } - crypto_otp_show.text = - Otp.calculateCode( - entry.totpSecret, - Date().time / (1000 * entry.totpPeriod), - entry.totpAlgorithm, - entry.digits) - } else { - // we only want to calculate and show HOTP if the user requests it - crypto_copy_otp.setOnClickListener { - if (settings.getBoolean("hotp_remember_check", false)) { - if (settings.getBoolean("hotp_remember_choice", false)) { - calculateAndCommitHotp(entry) - } else { - calculateHotp(entry) - } - } else { - // show a dialog asking permission to update the HOTP counter in the entry - val checkInflater = LayoutInflater.from(this@PgpActivity) - val checkLayout = checkInflater.inflate(R.layout.otp_confirm_layout, null) - val rememberCheck: CheckBox = - checkLayout.findViewById(R.id.hotp_remember_checkbox) - val dialogBuilder = MaterialAlertDialogBuilder(this@PgpActivity) - dialogBuilder.setView(checkLayout) - dialogBuilder.setMessage(R.string.dialog_update_body) - .setCancelable(false) - .setPositiveButton(R.string.dialog_update_positive) { _, _ -> - run { - calculateAndCommitHotp(entry) - if (rememberCheck.isChecked) { - settings.edit { - putBoolean("hotp_remember_check", true) - putBoolean("hotp_remember_choice", true) - } - } - } - } - .setNegativeButton(R.string.dialog_update_negative) { _, _ -> - run { - calculateHotp(entry) - settings.edit { - putBoolean("hotp_remember_check", true) - putBoolean("hotp_remember_choice", false) - } - } - } - val updateDialog = dialogBuilder.create() - updateDialog.setTitle(R.string.dialog_update_title) - updateDialog.show() - } - } - crypto_otp_show.setText(R.string.hotp_pending) - } - crypto_otp_show.typeface = monoTypeface - } else { - crypto_otp_show.visibility = View.GONE - crypto_otp_show_label.visibility = View.GONE - crypto_copy_otp.visibility = View.GONE - } - if (settings.getBoolean("copy_on_decrypt", true)) { copyPasswordToClipBoard() } @@ -559,12 +461,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { * Encrypts the password and the extra content */ private fun encrypt(copy: Boolean = false) { - // if HOTP was incremented, we leave fields as is; they have already been set - if (intent.getStringExtra("OPERATION") != "INCREMENT") { - editName = crypto_password_file_edit.text.toString().trim() - editPass = crypto_password_edit.text.toString() - editExtra = crypto_extra_edit.text.toString() - } + editName = crypto_password_file_edit.text.toString().trim() + editPass = crypto_password_edit.text.toString() + editExtra = crypto_extra_edit.text.toString() if (editName?.isEmpty() == true) { showSnackbar(resources.getString(R.string.file_toast_text)) @@ -687,43 +586,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { invalidateOptionsMenu() } - /** - * Writes updated HOTP counter to edit fields and encrypts - */ - private fun checkAndIncrementHotp() { - // we do not want to increment the HOTP counter if the user has edited the entry or has not - // generated an HOTP code - if (intent.getStringExtra("OPERATION") != "EDIT" && passwordEntry?.hotpIsIncremented() == true) { - editName = name.trim() - editPass = passwordEntry?.password - editExtra = passwordEntry?.extraContent - - val data = Intent(this, PgpActivity::class.java) - data.putExtra("OPERATION", "INCREMENT") - data.putExtra("fromDecrypt", true) - intent = data - encrypt() - } - } - - private fun calculateHotp(entry: PasswordEntry) { - copyOtpToClipBoard(Otp.calculateCode(entry.hotpSecret, entry.hotpCounter!! + 1, "sha1", entry.digits)) - crypto_otp_show.text = Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1, "sha1", entry.digits) - crypto_extra_show.text = entry.extraContent - } - - private fun calculateAndCommitHotp(entry: PasswordEntry) { - calculateHotp(entry) - entry.incrementHotp() - // we must set the result before encrypt() is called, since in - // some cases it is called during the finish() sequence - val returnIntent = Intent() - returnIntent.putExtra("NAME", name.trim()) - returnIntent.putExtra("OPERATION", "INCREMENT") - returnIntent.putExtra("needCommit", true) - setResult(RESULT_OK, returnIntent) - } - /** * Get the Key ids from OpenKeychain */ @@ -861,13 +723,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { showSnackbar(resources.getString(R.string.clipboard_username_toast_text)) } - private fun copyOtpToClipBoard(code: String) { - val clipboard = clipboard ?: return - val clip = ClipData.newPlainText("pgp_handler_result_pm", code) - clipboard.setPrimaryClip(clip) - showSnackbar(resources.getString(R.string.clipboard_otp_toast_text)) - } - private fun shareAsPlaintext() { if (findViewById(R.id.share_password_as_plaintext) == null) return @@ -957,13 +812,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { fun doOnPostExecute() { if (skip) return - checkAndIncrementHotp() if (crypto_password_show != null) { - // clear password; if decrypt changed to encrypt layout via edit button, no need - if (passwordEntry?.hotpIsIncremented() == false) { - setResult(RESULT_CANCELED) - } passwordEntry = null crypto_password_show.text = "" crypto_extra_show.text = "" diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Otp.java b/app/src/main/java/com/zeapo/pwdstore/utils/Otp.java deleted file mode 100644 index 07f73423..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Otp.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils; - -import org.apache.commons.codec.binary.Base32; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -import timber.log.Timber; - -public class Otp { - - private static final Base32 BASE_32 = new Base32(); - - private Otp() { - } - - public static String calculateCode( - String secret, long counter, String algorithm, String digits) { - String[] steam = { - "2", "3", "4", "5", "6", "7", "8", "9", "B", "C", "D", "F", "G", "H", "J", "K", "M", - "N", "P", "Q", "R", "T", "V", "W", "X", "Y" - }; - String ALGORITHM = "Hmac" + algorithm.toUpperCase(); - SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM); - - Mac mac; - try { - mac = Mac.getInstance(ALGORITHM); - mac.init(signingKey); - } catch (NoSuchAlgorithmException e) { - Timber.tag("TOTP").e(e, "%s unavailable - should never happen", ALGORITHM); - return null; - } catch (InvalidKeyException e) { - Timber.tag("TOTP").e(e, "Key is malformed"); - return null; - } - - byte[] digest = mac.doFinal(ByteBuffer.allocate(8).putLong(counter).array()); - int offset = digest[digest.length - 1] & 0xf; - byte[] code = Arrays.copyOfRange(digest, offset, offset + 4); - code[0] = (byte) (0x7f & code[0]); - String strCode = new BigInteger(code).toString(); - if (digits.equals("s")) { - StringBuilder output = new StringBuilder(); - int bigInt = new BigInteger(code).intValue(); - for (int i = 0; i != 5; i++) { - output.append(steam[bigInt % 26]); - bigInt /= 26; - } - return output.toString(); - } else return strCode.substring(strCode.length() - Integer.parseInt(digits)); - } -} diff --git a/app/src/main/res/layout/decrypt_layout.xml b/app/src/main/res/layout/decrypt_layout.xml index c9f3f922..932eefd6 100644 --- a/app/src/main/res/layout/decrypt_layout.xml +++ b/app/src/main/res/layout/decrypt_layout.xml @@ -160,56 +160,17 @@ app:layout_constraintTop_toBottomOf="@id/crypto_username_show_label" tools:visibility="visible" /> - - - - - - + app:layout_constraintTop_toBottomOf="@id/crypto_username_show" /> - - - - - - diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 380d58b7..ecb03154 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -63,7 +63,6 @@ تعديل كلمة السر نسخ كلمة السر نسخ إسم المستخدم - نسخ رمز الـ OTP شارك كنص مجرد آخِر تعديل %s diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 5b75d8d5..420c0ce4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -34,7 +34,6 @@ Heslo zkopírováno do schránky, máte %d sekund na jeho zkopírování. Heslo zkopírováno do schránky Jméno zkopírováno do schránky - OTP kód zkopírován do schránky Zadejte prosím jméno souboru Prosím zadejte cestu k souboru Nelze zadat prázdné heslo nebo další obsah @@ -102,7 +101,6 @@ Editovat heslo Kopírovat heslo Kopírovat jméno - Kopírovat OTP kód Sdílet v nešifrované podobě Naposled změněno %s diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8922fbdf..975f088f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -24,14 +24,12 @@ "%1$s editada usando Android Password Store." "%1$s eliminada del almacén usando Android Password Store." "Renombrado %1$s a %2$s usando Android Password Store.." - "Contador de HOTP incrementado para %1$s." ¡No se ha seleccionado ningún proveedor OpenGPG! Contraseña copiada al portapapeles, tienes %d segundos para pegarla. Contraseña copiada al portapapeles Nombre de usuario copiado al portapapeles - Código OTP copiado al portapapeles Por favor selecciona un nombre de archivo No puedes dejar la contraseña y el contenido extra ambos vacíos @@ -95,14 +93,8 @@ Editar contraseña Copiar contraseña Copiar nombre de usuario - Copiar código OTP Compartir como texto plano Última modificación %s - Atención - Actualizar registro - Dejar sin cambios - El contador HOTP será incrementado. Este cambio será confirmado. Si presionas "Dejar sin cambios", el código HOTP será mostrado, pero el contador no se cambiará. - Recordar elección Editar ajustes del servidor Git @@ -216,12 +208,10 @@ La subida fue rechazada por el servidor, Ejecuta \'Descargar desde servidor\' antes de subir o pulsa \'Sincronizar con servidor\' para realizar ambas acciones. El envío fue rechazado por el servidor, la razón: Ocurrió un error durante el envío: - Limpiar preferencia para incremento HOTP Recordar contraseñagit (inseguro) Hackish tools Abortar rebase Hash del commit Username: Nombre de usuario\n… o algún contenido extra Error al obtener la fecha de último cambio - Presiona copiar para calcular el HOTP diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ad5ad866..eb52acf9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -31,13 +31,11 @@ Editer %1$s depuis le dépôt. Retirer %1$s de la mémoire. Renommer %1$sà %2$s. - Incrémenter le compteur HOTP pour %1$s. Aucun prestataire OpenPGP sélectionné ! Mot de passe copié dans le presse papier, vous avez %d secondes pour coller celui-ci. Nom d\'utilisateur copié - Code OTP copié dans le presse-papier Renseignez un nom de fichier Vous ne pouvez pas utiliser un mot de passe vide ou des données supplémentaires vide @@ -103,14 +101,8 @@ Éditer le mot de passe Copier le mot de passe Copier le nom d\'utilisateur - Copier le code OTP Partager en clair Dernière modification le %s - Attention - Mettre à jour l\'entrée - Laisser inchangé - Le compteur HOTP est sur le point d’être incrémenté. Ce changement sera engagé. Si vous appuyez sur \"Laisser inchangé\", le code HOTP sera affiché mais le compteur ne sera pas modifié - Se souvenir de mon choix Editer les paramètres du serveur Git @@ -217,11 +209,9 @@ Poussée rejetée par le dépôt distant, raison: Pousser au dépôt distant sans avance rapide rejetée. Vérifiez la variable receive.denyNonFastForwards dans le fichier de configuration du répertoire de destination. Une erreur s\'est produite lors de l\'opération de poussée: - Effacer les préférences enregistrées pour l’incrémentation HOTP Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr) Outils de hack Commettre la clé nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire Failed to get last changed date - Appuyez sur copie pour calculer le HOTP diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca7ffaa0..d286bbb4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -31,14 +31,12 @@ Отредактирован %1$s из хранилища. Удалить %1$sиз хранилища. Переименовать %1$sв%2$s. - Увеличить значение HOTP счетчика для %1$s Не выбран провайдер OpenPGP! Пароль скопирован в буфер обмена, у вас есть %d секунд чтобы вставить его. Пароль скопирован в буфер обмена Имя пользователя скопировано в буфер обмена - OTP код скопирован в буфер обмена Пожалуйста, укажите имя файла Пожалуйста, задайте путь к файлу Вы не можете использовать пустой пароль или пустое поле информации @@ -108,14 +106,8 @@ Редактировать пароль Скопировать пароль Скопировать имя пользователя - Скопировать OTP код Поделиться в открытом виде Последние изменение %s - Внимание - Обновить запись - Оставить без изменений - HOTP счетчик будет увеличен. Это изменение будет сохранено. Если вы нажмете \"Оставить без изменений\", тогда HOTP код будет показан, но счетчик не изменится. - Запомнить мой выбор Изменить настройки сервера git @@ -278,7 +270,6 @@ Запись изменений была отклонена удаленным репозиторием, причина: Удаленный репозиторий отклонил запись изменений без быстрой перемотки вперед. Проверьте переменную receive.denyNonFastForwards в файле конфигурации репозитория назначения. В хоте операции записи изменений возникла ошибка: - Очистить сохраненные настройки для увеличения HOTP Заполнить парольную фразу в конфигурации приложнеия (небезопасно) Костыльные инструменты Прервать перебазирование и записать изменения в новую ветку @@ -286,7 +277,6 @@ Хэш-сумма изменений имя пользователя: какой-то другой дополнительный контент Failed to get last changed date - Нажмите скопировать чтобы расчитать HOTP Ошибка при подключении к сервису OpenKeychain SSH API Не найдено SSH API провайдеров. OpenKeychain установлен? Ожидаемое намерение SSH API не удалось diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b2f9c52..9b76e05f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,14 +42,12 @@ Edit password for %1$s using android password store. Remove %1$s from store. Rename %1$s to %2$s. - Increment HOTP counter for %1$s. No OpenPGP provider selected! Password copied to clipboard, you have %d seconds to paste it somewhere. Password copied to clipboard Username copied to clipboard - OTP code copied to clipboard Please provide a file name Please provide a file path You cannot use an empty password or empty extra content @@ -121,18 +119,11 @@ Password: Extra content: Username: - OTP: Edit password Copy password Copy username - Copy OTP code Share as plaintext Last changed %s - Attention - Update entry - Leave unchanged - The HOTP counter is about to be incremented. This change will be committed. If you press "Leave unchanged", the HOTP code will be shown, but the counter will not be changed. - Remember my choice Repository @@ -316,7 +307,6 @@ Error occurred during the push operation: Clear saved passphrase for local SSH key Clear saved HTTPS password - Clear saved preference for HOTP incrementing Remember key passphrase Hackish tools Abort rebase and push new branch @@ -324,7 +314,6 @@ Commit hash username: something other extra content Failed to get last changed date - Tap copy to calculate HOTP Failed to connect to OpenKeychain SSH API service. No SSH API provider found. Is OpenKeychain installed? SSH API pending intent failed diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml index c823bc6a..9e56ce8f 100644 --- a/app/src/main/res/xml/preference.xml +++ b/app/src/main/res/xml/preference.xml @@ -54,9 +54,6 @@ app:key="ssh_keygen" app:title="@string/pref_ssh_keygen_title" /> - diff --git a/app/src/test/java/com/zeapo/pwdstore/PasswordEntryTest.kt b/app/src/test/java/com/zeapo/pwdstore/PasswordEntryTest.kt new file mode 100644 index 00000000..2074f40b --- /dev/null +++ b/app/src/test/java/com/zeapo/pwdstore/PasswordEntryTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class PasswordEntryTest { + @Test fun testGetPassword() { + assertEquals("fooooo", PasswordEntry("fooooo\nbla\n").password) + assertEquals("fooooo", PasswordEntry("fooooo\nbla").password) + assertEquals("fooooo", PasswordEntry("fooooo\n").password) + assertEquals("fooooo", PasswordEntry("fooooo").password) + assertEquals("", PasswordEntry("\nblubb\n").password) + assertEquals("", PasswordEntry("\nblubb").password) + assertEquals("", PasswordEntry("\n").password) + assertEquals("", PasswordEntry("").password) + } + + @Test fun testGetExtraContent() { + assertEquals("bla\n", PasswordEntry("fooooo\nbla\n").extraContent) + assertEquals("bla", PasswordEntry("fooooo\nbla").extraContent) + assertEquals("", PasswordEntry("fooooo\n").extraContent) + assertEquals("", PasswordEntry("fooooo").extraContent) + assertEquals("blubb\n", PasswordEntry("\nblubb\n").extraContent) + assertEquals("blubb", PasswordEntry("\nblubb").extraContent) + assertEquals("", PasswordEntry("\n").extraContent) + assertEquals("", PasswordEntry("").extraContent) + } + + @Test fun testGetUsername() { + for (field in PasswordEntry.USERNAME_FIELDS) { + assertEquals("username", PasswordEntry("\n$field username").username) + assertEquals("username", PasswordEntry("\n${field.toUpperCase()} username").username) + } + assertEquals( + "username", + PasswordEntry("secret\nextra\nlogin: username\ncontent\n").username) + assertEquals( + "username", + PasswordEntry("\nextra\nusername: username\ncontent\n").username) + assertEquals( + "username", PasswordEntry("\nUSERNaMe: username\ncontent\n").username) + assertEquals("username", PasswordEntry("\nlogin: username").username) + assertEquals("foo@example.com", PasswordEntry("\nemail: foo@example.com").username) + assertEquals("username", PasswordEntry("\nidentity: username\nlogin: another_username").username) + assertEquals("username", PasswordEntry("\nLOGiN:username").username) + assertNull(PasswordEntry("secret\nextra\ncontent\n").username) + } + + @Test fun testHasUsername() { + assertTrue(PasswordEntry("secret\nextra\nlogin: username\ncontent\n").hasUsername()) + assertFalse(PasswordEntry("secret\nextra\ncontent\n").hasUsername()) + assertFalse(PasswordEntry("secret\nlogin failed\n").hasUsername()) + assertFalse(PasswordEntry("\n").hasUsername()) + assertFalse(PasswordEntry("").hasUsername()) + } +} -- cgit v1.2.3