Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ All notable changes to this project will be documented in this file.
- Allow sorting by recently used
- Add [Bromite](https://www.bromite.org/) and [Ungoogled Chromium](https://git.droidware.info/wchen342/ungoogled-chromium-android) to supported browsers list for Autofill
- Add ability to view the Git commit log
- Allow generating ECDSA and ED25519 keys for SSH

### Changed

- A descriptive error message is shown if no username is specified in the Git server settings
- Remove explicit protocol choice from Git server settings, it is now inferred from your URL
- 'Show hidden folders' is now 'Show hidden files and folders'
- Generated SSH keys are now stored in the Android Keystore if available, and encrypted at rest otherwise
- Allow using device's screen lock credentials to secure generated SSH key

### Fixed

Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
import java.util.Properties
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import java.util.Properties

plugins {
kotlin("android")
Expand Down Expand Up @@ -113,11 +113,11 @@ dependencies {
implementation(Dependencies.FirstParty.zxing_android_embedded)

implementation(Dependencies.ThirdParty.commons_codec)
implementation(Dependencies.ThirdParty.eddsa)
implementation(Dependencies.ThirdParty.fastscroll)
implementation(Dependencies.ThirdParty.jgit) {
exclude(group = "org.apache.httpcomponents", module = "httpclient")
}
implementation(Dependencies.ThirdParty.jsch)
implementation(Dependencies.ThirdParty.sshj)
implementation(Dependencies.ThirdParty.bouncycastle)
implementation(Dependencies.ThirdParty.plumber)
Expand Down
1 change: 0 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
-keepattributes SourceFile,LineNumberTable
-dontobfuscate

-keep class com.jcraft.jsch.**
-keep class org.eclipse.jgit.internal.JGitText { *; }
-keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; }
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/zeapo/pwdstore/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import com.github.ajalt.timberkt.Timber.DebugTree
import com.github.ajalt.timberkt.Timber.plant
import com.zeapo.pwdstore.git.sshj.setUpBouncyCastleForSshj
import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.sharedPrefs
import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.sharedPrefs

@Suppress("Unused")
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
Expand Down Expand Up @@ -45,7 +45,8 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
}

private fun setNightMode() {
AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME) ?: getString(R.string.app_theme_def)) {
AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME)
?: getString(R.string.app_theme_def)) {
"light" -> MODE_NIGHT_NO
"dark" -> MODE_NIGHT_YES
"follow_system" -> MODE_NIGHT_FOLLOW_SYSTEM
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class ClipboardService : Service() {

ACTION_START -> {
val time = try {
Integer.parseInt(settings.getString(PreferenceKeys.GENERAL_SHOW_TIME) ?: "45")
Integer.parseInt(settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)
?: "45")
} catch (e: NumberFormatException) {
45
}
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/zeapo/pwdstore/Migrations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ private fun migrateToGitUrlBasedConfig(context: Context) {
if (!serverPath.startsWith('/'))
null
else
// We have to specify the ssh scheme as this is the only way to pass a custom
// port.
// We have to specify the ssh scheme as this is the only way to pass a custom
// port.
"ssh://$userPart$hostnamePart$portPart$serverPath"
}
}
Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import com.zeapo.pwdstore.crypto.BasePgpActivity.Companion.getLongName
import com.zeapo.pwdstore.crypto.DecryptActivity
import com.zeapo.pwdstore.crypto.PasswordCreationActivity
import com.zeapo.pwdstore.git.BaseGitActivity
import com.zeapo.pwdstore.git.log.GitLogActivity
import com.zeapo.pwdstore.git.GitOperationActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
import com.zeapo.pwdstore.git.config.AuthMode
Expand Down
82 changes: 33 additions & 49 deletions app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import android.provider.Settings
import android.text.TextUtils
import android.view.MenuItem
Expand Down Expand Up @@ -45,6 +44,7 @@ import com.zeapo.pwdstore.autofill.oreo.getInstalledBrowsersWithAutofillSupportL
import com.zeapo.pwdstore.crypto.BasePgpActivity
import com.zeapo.pwdstore.git.GitConfigActivity
import com.zeapo.pwdstore.git.GitServerConfigActivity
import com.zeapo.pwdstore.git.sshj.SshKey
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
Expand All @@ -56,7 +56,6 @@ import com.zeapo.pwdstore.utils.getEncryptedPrefs
import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.sharedPrefs
import java.io.File
import java.io.IOException

typealias ClickListener = Preference.OnPreferenceClickListener
typealias ChangeListener = Preference.OnPreferenceChangeListener
Expand All @@ -69,6 +68,7 @@ class UserPreference : AppCompatActivity() {

private var autoFillEnablePreference: SwitchPreferenceCompat? = null
private var clearSavedPassPreference: Preference? = null
private var viewSshKeyPreference: Preference? = null
private lateinit var autofillDependencies: List<Preference>
private lateinit var oreoAutofillDependencies: List<Preference>
private lateinit var prefsActivity: UserPreference
Expand All @@ -89,8 +89,8 @@ class UserPreference : AppCompatActivity() {
val gitConfigPreference = findPreference<Preference>(PreferenceKeys.GIT_CONFIG)
val sshKeyPreference = findPreference<Preference>(PreferenceKeys.SSH_KEY)
val sshKeygenPreference = findPreference<Preference>(PreferenceKeys.SSH_KEYGEN)
viewSshKeyPreference = findPreference<Preference>(PreferenceKeys.SSH_SEE_KEY)
clearSavedPassPreference = findPreference(PreferenceKeys.CLEAR_SAVED_PASS)
val viewSshKeyPreference = findPreference<Preference>(PreferenceKeys.SSH_SEE_KEY)
val deleteRepoPreference = findPreference<Preference>(PreferenceKeys.GIT_DELETE_REPO)
val externalGitRepositoryPreference = findPreference<Preference>(PreferenceKeys.GIT_EXTERNAL)
val selectExternalGitRepositoryPreference = findPreference<Preference>(PreferenceKeys.PREF_SELECT_EXTERNAL)
Expand Down Expand Up @@ -141,8 +141,8 @@ class UserPreference : AppCompatActivity() {
// Misc preferences
val appVersionPreference = findPreference<Preference>(PreferenceKeys.APP_VERSION)

selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) ?: getString(R.string.no_repo_selected)
viewSshKeyPreference?.isVisible = sharedPreferences.getBoolean(PreferenceKeys.USE_GENERATED_KEY, false)
selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
?: getString(R.string.no_repo_selected)
deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
clearClipboard20xPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toInt() != 0
openkeystoreIdPreference?.isVisible = sharedPreferences.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID)?.isNotEmpty()
Expand Down Expand Up @@ -226,7 +226,8 @@ class UserPreference : AppCompatActivity() {
}

selectExternalGitRepositoryPreference?.summary =
sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO) ?: context.getString(R.string.no_repo_selected)
sharedPreferences.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
?: context.getString(R.string.no_repo_selected)
selectExternalGitRepositoryPreference?.onPreferenceClickListener = ClickListener {
prefsActivity.selectExternalGitRepository()
true
Expand Down Expand Up @@ -393,6 +394,10 @@ class UserPreference : AppCompatActivity() {
}
}

private fun updateViewSshPubkeyPref() {
viewSshKeyPreference?.isVisible = SshKey.canShowSshPublicKey
}

private fun onEnableAutofillClick() {
if (prefsActivity.isAccessibilityServiceEnabled) {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
Expand Down Expand Up @@ -451,6 +456,7 @@ class UserPreference : AppCompatActivity() {
super.onResume()
updateAutofillSettings()
updateClearSavedPassphrasePrefs()
updateViewSshPubkeyPref()
}
}

Expand Down Expand Up @@ -532,29 +538,18 @@ class UserPreference : AppCompatActivity() {
}
}

/**
* Opens a file explorer to import the private key
*/
private fun getSshKey() {
private fun importSshKey() {
registerForActivityResult(OpenDocument()) { uri: Uri? ->
if (uri == null) return@registerForActivityResult
try {
copySshKey(uri)
SshKey.import(uri)

Toast.makeText(
this,
this.resources.getString(R.string.ssh_key_success_dialog_title),
Toast.LENGTH_LONG
).show()
val prefs = sharedPrefs

prefs.edit { putBoolean(PreferenceKeys.USE_GENERATED_KEY, false) }
getEncryptedPrefs("git_operation").edit { remove(PreferenceKeys.SSH_KEY_LOCAL_PASSPHRASE) }

// Delete the public key from generation
File("""$filesDir/.ssh_key.pub""").delete()
setResult(RESULT_OK)

finish()
} catch (e: Exception) {
MaterialAlertDialogBuilder(this)
Expand All @@ -566,6 +561,25 @@ class UserPreference : AppCompatActivity() {
}.launch(arrayOf("*/*"))
}

/**
* Opens a file explorer to import the private key
*/
private fun getSshKey() {
if (SshKey.exists) {
MaterialAlertDialogBuilder(this).run {
setTitle(R.string.ssh_keygen_existing_title)
setMessage(R.string.ssh_keygen_existing_message)
setPositiveButton(R.string.ssh_keygen_existing_replace) { _, _ ->
importSshKey()
}
setNegativeButton(R.string.ssh_keygen_existing_keep) { _, _ -> }
show()
}
} else {
importSshKey()
}
}

/**
* Exports the passwords
*/
Expand Down Expand Up @@ -638,36 +652,6 @@ class UserPreference : AppCompatActivity() {
}.launch(arrayOf("*/*"))
}

@Throws(IllegalArgumentException::class, IOException::class)
private fun copySshKey(uri: Uri) {
// First check whether the content at uri is likely an SSH private key.
val fileSize = contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
?.use { cursor ->
// Cursor returns only a single row.
cursor.moveToFirst()
cursor.getInt(0)
} ?: throw IOException(getString(R.string.ssh_key_does_not_exist))

// We assume that an SSH key's ideal size is > 0 bytes && < 100 kilobytes.
if (fileSize > 100_000 || fileSize == 0)
throw IllegalArgumentException(getString(R.string.ssh_key_import_error_not_an_ssh_key_message))

val sshKeyInputStream = contentResolver.openInputStream(uri)
?: throw IOException(getString(R.string.ssh_key_does_not_exist))
val lines = sshKeyInputStream.bufferedReader().readLines()

// The file must have more than 2 lines, and the first and last line must have private key
// markers.
if (lines.size < 2 ||
!Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) ||
!Regex("END .* PRIVATE KEY").containsMatchIn(lines.last())
)
throw IllegalArgumentException(getString(R.string.ssh_key_import_error_not_an_ssh_key_message))

// Canonicalize line endings to '\n'.
File("$filesDir/.ssh_key").writeText(lines.joinToString("\n"))
}

private val isAccessibilityServiceEnabled: Boolean
get() {
val am = getSystemService<AccessibilityManager>() ?: return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import android.view.autofill.AutofillId
import android.widget.RemoteViews
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.preference.PreferenceManager
import com.github.ajalt.timberkt.Timber.tag
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.R
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/zeapo/pwdstore/git/ErrorMessages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ sealed class GitException(@StringRes res: Int, vararg fmt: String) : Exception(b
override val message = super.message!!

companion object {

private fun buildMessage(@StringRes res: Int, vararg fmt: String) = Application.instance.resources.getString(res, *fmt)
}

/**
* Encapsulates possible errors from a [org.eclipse.jgit.api.PullCommand].
*/
sealed class PullException(@StringRes res: Int, vararg fmt: String) : GitException(res, *fmt) {

object PullRebaseFailed : PullException(R.string.git_pull_fail_error)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.git.GitException.PullException
import com.zeapo.pwdstore.git.GitException.PushException
import com.zeapo.pwdstore.git.config.GitSettings
import com.zeapo.pwdstore.git.sshj.SshjSessionFactory
import com.zeapo.pwdstore.git.operation.GitOperation
import com.zeapo.pwdstore.git.sshj.SshjSessionFactory
import com.zeapo.pwdstore.utils.Result
import com.zeapo.pwdstore.utils.snackbar
import kotlinx.coroutines.Dispatchers
Expand Down
Loading