summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Henneke <FabianHenneke@users.noreply.github.com>2020-04-16 15:04:55 +0200
committerGitHub <noreply@github.com>2020-04-16 18:34:55 +0530
commit8b4751f8250dea3efe04c73a87bdfe2926e2ac3f (patch)
tree22c5e724b254c939567ea062615e9e03d4f9ef5c
parentf269bc7d28f43743648a346288749f5b7f878402 (diff)
Improve SSH private key validation (#713)
* Improve SSH private key validation * Address review comment
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt87
-rw-r--r--app/src/main/res/values/strings.xml2
2 files changed, 30 insertions, 59 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 4590e678..716a9192 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -522,49 +522,32 @@ class UserPreference : AppCompatActivity() {
@Throws(IllegalArgumentException::class, IOException::class)
private fun copySshKey(uri: Uri) {
- // See metadata from document to validate SSH key
- contentResolver.query(uri, null, null, null, null, null)?.use { cursor ->
- val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
- // cursor returns only 1 row
- cursor.moveToFirst()
- // see file's metadata
- val fileSize = cursor.getInt(sizeIndex)
- // We assume that an SSH key's ideal size is > 0 bytes && < 100 kilobytes.
- if (fileSize > 100000 || fileSize == 0) {
- throw IllegalArgumentException("Wrong file type selected")
- } else {
- // Validate BEGIN and END markers
- val lines = contentResolver.openInputStream(uri)?.bufferedReader()?.readLines()
- // The file must have more than 2 lines, and the first and last line must have
- // OpenSSH key markers.
- if (lines != null &&
- lines.size > 2 &&
- !lines[0].contains("BEGIN OPENSSH PRIVATE KEY") &&
- !lines[lines.size - 1].contains("END OPENSSH PRIVATE KEY")) {
- throw IllegalArgumentException("Wrong file type selected")
- }
- }
- }
+ // 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)
- if (sshKeyInputStream != null) {
-
- val internalKeyFile = File("""$filesDir/.ssh_key""")
-
- if (internalKeyFile.exists()) {
- internalKeyFile.delete()
- internalKeyFile.createNewFile()
- }
-
- val sshKeyOutputSteam = internalKeyFile.outputStream()
-
- sshKeyInputStream.copyTo(sshKeyOutputSteam, 1024)
-
- sshKeyInputStream.close()
- sshKeyOutputSteam.close()
- } else {
- Toast.makeText(this, getString(R.string.ssh_key_does_not_exist), Toast.LENGTH_LONG).show()
- }
+ ?: 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
@@ -622,23 +605,11 @@ class UserPreference : AppCompatActivity() {
finish()
} catch (e: Exception) {
- when (e) {
- is IOException,
- is IllegalArgumentException -> {
- MaterialAlertDialogBuilder(this)
- .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))
- .setMessage(getString(R.string.ssh_key_import_error_not_an_ssh_key_message))
- .setPositiveButton(resources.getString(R.string.dialog_ok), null)
- .show()
- }
- else -> {
- MaterialAlertDialogBuilder(this)
- .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))
- .setMessage(resources.getString(R.string.ssh_key_error_dialog_text) + e.message)
- .setPositiveButton(resources.getString(R.string.dialog_ok), null)
- .show()
- }
- }
+ MaterialAlertDialogBuilder(this)
+ .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))
+ .setMessage(e.message)
+ .setPositiveButton(resources.getString(R.string.dialog_ok), null)
+ .show()
}
}
EDIT_GIT_INFO -> {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0b9f908e..bd2aa75e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -148,6 +148,7 @@
<string name="pref_show_time_summary">Set the time (in seconds) you want the password to be in clipboard. 0 means forever. Current value: %1$s</string>
<string name="pref_copy_title">Automatically copy password</string>
<string name="pref_copy_dialog_title">Automatically copy the password to the clipboard after decryption was successful.</string>
+ <string name="ssh_key_import_error_not_an_ssh_key_message">Selected file does not appear to be an SSH private key.</string>
<string name="ssh_key_success_dialog_title">SSH-key imported</string>
<string name="ssh_key_error_dialog_title">Key import error</string>
<string name="ssh_key_error_dialog_text">Message : \n</string>
@@ -348,5 +349,4 @@
<string name="theme_dark">Dark</string>
<string name="theme_battery_saver">Set by Battery Saver</string>
<string name="theme_follow_system">System default</string>
- <string name="ssh_key_import_error_not_an_ssh_key_message">Selected file does not appear to be an SSH key</string>
</resources>