Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
Remove HOTP/TOTP support (#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
Harsh Shandilya authored May 28, 2020
1 parent ffcbabc commit e7463ec
Show file tree
Hide file tree
Showing 17 changed files with 13 additions and 484 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]

### Changed
- **BREAKING**: Remove support for HOTP/TOTP secrets - Please use FIDO keys or a dedicated app like [Aegis](https://github.com/beemdevelopment/Aegis) or [andOTP](https://github.com/andOTP/andOTP).
- Reduce Autofill false positives on username fields by removing "name" from list of heuristic terms
- Reduced app size
- Improve IME experience with server config screen
Expand Down
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
104 changes: 2 additions & 102 deletions app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,25 @@
*/
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"))

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()
}
Expand All @@ -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 ->
Expand All @@ -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 {
Expand Down
6 changes: 1 addition & 5 deletions app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
8 changes: 0 additions & 8 deletions app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit e7463ec

Please sign in to comment.