Remove HOTP/TOTP support (#806)

This commit is contained in:
Harsh Shandilya 2020-05-28 22:42:13 +05:30 committed by GitHub
parent ffcbabc2f4
commit e7463ec24c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 13 additions and 484 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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()

View file

@ -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

View file

@ -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()
}
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<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 = ""

View file

@ -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));
}
}

View file

@ -160,56 +160,17 @@
app:layout_constraintTop_toBottomOf="@id/crypto_username_show_label"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/crypto_copy_otp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/crypto_username_show"
android:layout_alignParentEnd="true"
android:background="?android:attr/windowBackground"
android:contentDescription="@string/copy_otp"
android:src="@drawable/ic_content_copy"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/crypto_otp_show_label"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/crypto_otp_show_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/crypto_username_show"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/crypto_copy_otp"
android:text="@string/otp"
android:textColor="?android:attr/textColor"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/crypto_username_show" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/crypto_otp_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/crypto_otp_show_label"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/crypto_copy_otp"
android:textColor="?android:attr/textColor"
android:textIsSelectable="true"
android:typeface="monospace"
app:layout_constraintTop_toBottomOf="@id/crypto_otp_show_label" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/crypto_extra_show_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/crypto_otp_show"
android:layout_below="@id/crypto_username_show"
android:layout_alignParentStart="true"
android:text="@string/extra_content"
android:textColor="?android:attr/textColor"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/crypto_otp_show" />
app:layout_constraintTop_toBottomOf="@id/crypto_username_show" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/crypto_extra_show"

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:id="@+id/hotp_remember_checkbox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@string/dialog_update_check"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -63,7 +63,6 @@
<string name="edit_password">تعديل كلمة السر</string>
<string name="copy_password">نسخ كلمة السر</string>
<string name="copy_username">نسخ إسم المستخدم</string>
<string name="copy_otp">نسخ رمز الـ OTP</string>
<string name="share_as_plaintext">شارك كنص مجرد</string>
<string name="last_changed">آخِر تعديل %s</string>
<!-- Preferences -->

View file

@ -34,7 +34,6 @@
<string name="clipboard_password_toast_text">Heslo zkopírováno do schránky, máte %d sekund na jeho zkopírování.</string>
<string name="clipboard_password_no_clear_toast_text">Heslo zkopírováno do schránky</string>
<string name="clipboard_username_toast_text">Jméno zkopírováno do schránky</string>
<string name="clipboard_otp_toast_text">OTP kód zkopírován do schránky</string>
<string name="file_toast_text">Zadejte prosím jméno souboru</string>
<string name="path_toast_text">Prosím zadejte cestu k souboru</string>
<string name="empty_toast_text">Nelze zadat prázdné heslo nebo další obsah</string>
@ -102,7 +101,6 @@
<string name="edit_password">Editovat heslo</string>
<string name="copy_password">Kopírovat heslo</string>
<string name="copy_username">Kopírovat jméno</string>
<string name="copy_otp">Kopírovat OTP kód</string>
<string name="share_as_plaintext">Sdílet v nešifrované podobě</string>
<string name="last_changed">Naposled změněno %s</string>
<!-- Preferences -->

View file

@ -24,14 +24,12 @@
<string name="git_commit_edit_text">"%1$s editada usando Android Password Store."</string>
<string name="git_commit_remove_text">"%1$s eliminada del almacén usando Android Password Store."</string>
<string name="git_commit_move_text">"Renombrado %1$s a %2$s usando Android Password Store.."</string>
<string name="git_commit_increment_text">"Contador de HOTP incrementado para %1$s."</string>
<!-- PGPHandler -->
<string name="provider_toast_text">¡No se ha seleccionado ningún proveedor OpenGPG!</string>
<string name="clipboard_password_toast_text">Contraseña copiada al portapapeles, tienes %d segundos para pegarla.</string>
<string name="clipboard_password_no_clear_toast_text">Contraseña copiada al portapapeles</string>
<string name="clipboard_username_toast_text">Nombre de usuario copiado al portapapeles</string>
<string name="clipboard_otp_toast_text">Código OTP copiado al portapapeles</string>
<string name="file_toast_text">Por favor selecciona un nombre de archivo</string>
<string name="empty_toast_text">No puedes dejar la contraseña y el contenido extra ambos vacíos</string>
@ -95,14 +93,8 @@
<string name="edit_password">Editar contraseña</string>
<string name="copy_password">Copiar contraseña</string>
<string name="copy_username">Copiar nombre de usuario</string>
<string name="copy_otp">Copiar código OTP</string>
<string name="share_as_plaintext">Compartir como texto plano</string>
<string name="last_changed">Última modificación %s</string>
<string name="dialog_update_title">Atención</string>
<string name="dialog_update_positive">Actualizar registro</string>
<string name="dialog_update_negative">Dejar sin cambios</string>
<string name="dialog_update_body">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á.</string>
<string name="dialog_update_check">Recordar elección</string>
<!-- Preferences -->
<string name="pref_edit_server_info">Editar ajustes del servidor Git</string>
@ -216,12 +208,10 @@
<string name="git_push_nff_error">La subida fue rechazada por el servidor, Ejecuta \'Descargar desde servidor\' antes de subir o pulsa \'Sincronizar con servidor\' para realizar ambas acciones.</string>
<string name="git_push_generic_error">El envío fue rechazado por el servidor, la razón:</string>
<string name="jgit_error_push_dialog_text">Ocurrió un error durante el envío:</string>
<string name="hotp_remember_clear_choice">Limpiar preferencia para incremento HOTP</string>
<string name="remember_the_passphrase">Recordar contraseñagit (inseguro)</string>
<string name="hackish_tools">Hackish tools</string>
<string name="abort_rebase">Abortar rebase</string>
<string name="commit_hash">Hash del commit</string>
<string name="crypto_extra_edit_hint">Username: Nombre de usuario\n… o algún contenido extra</string>
<string name="get_last_changed_failed">Error al obtener la fecha de último cambio</string>
<string name="hotp_pending">Presiona copiar para calcular el HOTP</string>
</resources>

View file

@ -31,13 +31,11 @@
<string name="git_commit_edit_text">Editer %1$s depuis le dépôt.</string>
<string name="git_commit_remove_text">Retirer %1$s de la mémoire.</string>
<string name="git_commit_move_text">Renommer %1$sà %2$s. </string>
<string name="git_commit_increment_text">Incrémenter le compteur HOTP pour %1$s.</string>
<!-- PGPHandler -->
<string name="provider_toast_text">Aucun prestataire OpenPGP sélectionné !</string>
<string name="clipboard_password_toast_text">Mot de passe copié dans le presse papier, vous avez %d secondes pour coller celui-ci.</string>
<string name="clipboard_username_toast_text">Nom d\'utilisateur copié</string>
<string name="clipboard_otp_toast_text">Code OTP copié dans le presse-papier</string>
<string name="file_toast_text">Renseignez un nom de fichier</string>
<string name="empty_toast_text">Vous ne pouvez pas utiliser un mot de passe vide ou des données supplémentaires vide</string>
@ -103,14 +101,8 @@
<string name="edit_password">Éditer le mot de passe</string>
<string name="copy_password">Copier le mot de passe</string>
<string name="copy_username">Copier le nom d\'utilisateur</string>
<string name="copy_otp">Copier le code OTP</string>
<string name="share_as_plaintext">Partager en clair</string>
<string name="last_changed">Dernière modification le %s</string>
<string name="dialog_update_title">Attention</string>
<string name="dialog_update_positive">Mettre à jour l\'entrée</string>
<string name="dialog_update_negative">Laisser inchangé</string>
<string name="dialog_update_body">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é</string>
<string name="dialog_update_check">Se souvenir de mon choix</string>
<!-- Preferences -->
<string name="pref_edit_server_info">Editer les paramètres du serveur Git</string>
@ -217,11 +209,9 @@
<string name="git_push_generic_error">Poussée rejetée par le dépôt distant, raison:</string>
<string name="git_push_other_error">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.</string>
<string name="jgit_error_push_dialog_text">Une erreur s\'est produite lors de l\'opération de poussée:</string>
<string name="hotp_remember_clear_choice">Effacer les préférences enregistrées pour lincrémentation HOTP</string>
<string name="remember_the_passphrase">Se rappeler de la phrase secrète dans la configuration de l\'application (peu sûr)</string>
<string name="hackish_tools">Outils de hack</string>
<string name="commit_hash">Commettre la clé</string>
<string name="crypto_extra_edit_hint">nom d\'utilisateur: quelque chose d\'autre contenu supplémentaire</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="hotp_pending">Appuyez sur copie pour calculer le HOTP</string>
</resources>

View file

@ -31,14 +31,12 @@
<string name="git_commit_edit_text">Отредактирован %1$s из хранилища.</string>
<string name="git_commit_remove_text">Удалить %1$sиз хранилища.</string>
<string name="git_commit_move_text">Переименовать %1$sв%2$s.</string>
<string name="git_commit_increment_text">Увеличить значение HOTP счетчика для %1$s</string>
<!-- PGPHandler -->
<string name="provider_toast_text">Не выбран провайдер OpenPGP!</string>
<string name="clipboard_password_toast_text">Пароль скопирован в буфер обмена, у вас есть %d секунд чтобы вставить его.</string>
<string name="clipboard_password_no_clear_toast_text">Пароль скопирован в буфер обмена</string>
<string name="clipboard_username_toast_text">Имя пользователя скопировано в буфер обмена</string>
<string name="clipboard_otp_toast_text">OTP код скопирован в буфер обмена</string>
<string name="file_toast_text">Пожалуйста, укажите имя файла</string>
<string name="path_toast_text">Пожалуйста, задайте путь к файлу</string>
<string name="empty_toast_text">Вы не можете использовать пустой пароль или пустое поле информации</string>
@ -108,14 +106,8 @@
<string name="edit_password">Редактировать пароль</string>
<string name="copy_password">Скопировать пароль</string>
<string name="copy_username">Скопировать имя пользователя</string>
<string name="copy_otp">Скопировать OTP код</string>
<string name="share_as_plaintext">Поделиться в открытом виде</string>
<string name="last_changed">Последние изменение %s</string>
<string name="dialog_update_title">Внимание</string>
<string name="dialog_update_positive">Обновить запись</string>
<string name="dialog_update_negative">Оставить без изменений</string>
<string name="dialog_update_body">HOTP счетчик будет увеличен. Это изменение будет сохранено. Если вы нажмете \"Оставить без изменений\", тогда HOTP код будет показан, но счетчик не изменится.</string>
<string name="dialog_update_check">Запомнить мой выбор</string>
<!-- Preferences -->
<string name="pref_edit_server_info">Изменить настройки сервера git</string>
@ -278,7 +270,6 @@
<string name="git_push_generic_error">Запись изменений была отклонена удаленным репозиторием, причина:</string>
<string name="git_push_other_error">Удаленный репозиторий отклонил запись изменений без быстрой перемотки вперед. Проверьте переменную receive.denyNonFastForwards в файле конфигурации репозитория назначения.</string>
<string name="jgit_error_push_dialog_text">В хоте операции записи изменений возникла ошибка:</string>
<string name="hotp_remember_clear_choice">Очистить сохраненные настройки для увеличения HOTP</string>
<string name="remember_the_passphrase">Заполнить парольную фразу в конфигурации приложнеия (небезопасно)</string>
<string name="hackish_tools">Костыльные инструменты</string>
<string name="abort_rebase">Прервать перебазирование и записать изменения в новую ветку</string>
@ -286,7 +277,6 @@
<string name="commit_hash">Хэш-сумма изменений</string>
<string name="crypto_extra_edit_hint">имя пользователя: какой-то другой дополнительный контент</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="hotp_pending">Нажмите скопировать чтобы расчитать HOTP</string>
<string name="openkeychain_ssh_api_connect_fail">Ошибка при подключении к сервису OpenKeychain SSH API</string>
<string name="no_ssh_api_provider">Не найдено SSH API провайдеров. OpenKeychain установлен?</string>
<string name="ssh_api_pending_intent_failed">Ожидаемое намерение SSH API не удалось</string>

View file

@ -42,14 +42,12 @@
<string name="git_commit_edit_text">Edit password for %1$s using android password store.</string>
<string name="git_commit_remove_text">Remove %1$s from store.</string>
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
<string name="git_commit_increment_text">Increment HOTP counter for %1$s.</string>
<!-- PGPHandler -->
<string name="provider_toast_text">No OpenPGP provider selected!</string>
<string name="clipboard_password_toast_text">Password copied to clipboard, you have %d seconds to paste it somewhere.</string>
<string name="clipboard_password_no_clear_toast_text">Password copied to clipboard</string>
<string name="clipboard_username_toast_text">Username copied to clipboard</string>
<string name="clipboard_otp_toast_text">OTP code copied to clipboard</string>
<string name="file_toast_text">Please provide a file name</string>
<string name="path_toast_text">Please provide a file path</string>
<string name="empty_toast_text">You cannot use an empty password or empty extra content</string>
@ -121,18 +119,11 @@
<string name="password">Password:</string>
<string name="extra_content">Extra content:</string>
<string name="username">Username:</string>
<string name="otp" translatable="false">OTP:</string>
<string name="edit_password">Edit password</string>
<string name="copy_password">Copy password</string>
<string name="copy_username">Copy username</string>
<string name="copy_otp">Copy OTP code</string>
<string name="share_as_plaintext">Share as plaintext</string>
<string name="last_changed">Last changed %s</string>
<string name="dialog_update_title">Attention</string>
<string name="dialog_update_positive">Update entry</string>
<string name="dialog_update_negative">Leave unchanged</string>
<string name="dialog_update_body">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.</string>
<string name="dialog_update_check">Remember my choice</string>
<!-- Preferences -->
<string name="pref_repository_title">Repository</string>
@ -316,7 +307,6 @@
<string name="jgit_error_push_dialog_text">Error occurred during the push operation:</string>
<string name="clear_saved_passphrase_ssh">Clear saved passphrase for local SSH key</string>
<string name="clear_saved_passphrase_https">Clear saved HTTPS password</string>
<string name="hotp_remember_clear_choice">Clear saved preference for HOTP incrementing</string>
<string name="remember_the_passphrase">Remember key passphrase</string>
<string name="hackish_tools">Hackish tools</string>
<string name="abort_rebase">Abort rebase and push new branch</string>
@ -324,7 +314,6 @@
<string name="commit_hash">Commit hash</string>
<string name="crypto_extra_edit_hint">username: something other extra content</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="hotp_pending">Tap copy to calculate HOTP</string>
<string name="openkeychain_ssh_api_connect_fail">Failed to connect to OpenKeychain SSH API service.</string>
<string name="no_ssh_api_provider">No SSH API provider found. Is OpenKeychain installed?</string>
<string name="ssh_api_pending_intent_failed">SSH API pending intent failed</string>

View file

@ -54,9 +54,6 @@
app:key="ssh_keygen"
app:title="@string/pref_ssh_keygen_title" />
<Preference app:key="clear_saved_pass" />
<Preference
app:key="hotp_remember_clear_choice"
app:title="@string/hotp_remember_clear_choice" />
<Preference
app:key="ssh_openkeystore_clear_keyid"
app:title="@string/ssh_openkeystore_clear_keyid" />

View file

@ -60,45 +60,4 @@ class PasswordEntryTest {
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)
}
}