diff options
Diffstat (limited to 'app/src/main/java')
5 files changed, 7 insertions, 332 deletions
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>): 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<Preference>("ssh_key") val sshKeygenPreference = findPreference<Preference>("ssh_keygen") clearSavedPassPreference = findPreference("clear_saved_pass") - val clearHotpIncrementPreference = findPreference<Preference>("hotp_remember_clear_choice") val viewSshKeyPreference = findPreference<Preference>("ssh_see_key") val deleteRepoPreference = findPreference<Preference>("git_delete_repo") val externalGitRepositoryPreference = findPreference<Preference>("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<ClipboardManager>() } @@ -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)) @@ -688,43 +587,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound { } /** - * 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 */ private fun getKeyIds(receivedIntent: Intent? = null) { @@ -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<View>(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)); - } -} |