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

Commit

Permalink
Use SSHJ for SSH public key authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
fmeum committed May 27, 2020
1 parent 1566524 commit 3546f4e
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 291 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ dependencies {
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
}
implementation deps.third_party.jsch
implementation deps.third_party.sshj
implementation deps.third_party.bouncycastle
implementation deps.third_party.openpgp_ktx
implementation deps.third_party.ssh_auth
implementation deps.third_party.timber
Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,11 @@ abstract class BaseGitActivity : AppCompatActivity() {
}
if (PasswordRepository.isInitialized)
PasswordRepository.addRemote("origin", newUrl, true)
// HTTPS authentication sends the password to the server, so we must wipe the password when
// the server is changed.
if (previousUrl.isNotEmpty() && newUrl != previousUrl && protocol == Protocol.Https)
// When the server changes, remote password and host key file should be deleted.
if (previousUrl.isNotEmpty() && newUrl != previousUrl) {
encryptedSettings.edit { remove("https_password") }
File("$filesDir/.host_key").delete()
}
url = newUrl
return GitUpdateUrlResult.Ok
}
Expand Down Expand Up @@ -201,8 +202,7 @@ abstract class BaseGitActivity : AppCompatActivity() {
return
}
}
op.executeAfterAuthentication(connectionMode, serverUser,
File("$filesDir/.ssh_key"), identity)
op.executeAfterAuthentication(connectionMode, serverUser, identity)
} catch (e: Exception) {
e.printStackTrace()
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
.execute(*this.commands.toTypedArray())
}

override fun onError(errorMessage: String) {
override fun onError(err: Exception) {
MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occurred when checking out another branch operation $errorMessage")
.setMessage("Error occurred when checking out another branch operation ${err.message}")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
callingActivity.finish()
}.show()
Expand Down
31 changes: 3 additions & 28 deletions app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,43 +34,18 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
return this
}

/**
* sets the authentication for user/pwd scheme
*
* @param username the username
* @param password the password
* @return the current object
*/
public override fun setAuthentication(username: String, password: String): CloneOperation {
super.setAuthentication(username, password)
return this
}

/**
* sets the authentication for the ssh-key scheme
*
* @param sshKey the ssh-key file
* @param username the username
* @param passphrase the passphrase
* @return the current object
*/
public override fun setAuthentication(sshKey: File, username: String, passphrase: String): CloneOperation {
super.setAuthentication(sshKey, username, passphrase)
return this
}

override fun execute() {
(this.command as? CloneCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, false, this, Intent()).execute(this.command)
}

override fun onError(errorMessage: String) {
super.onError(errorMessage)
override fun onError(err: Exception) {
super.onError(err)
MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occurred during the clone operation, " +
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
errorMessage +
err.message +
"\nPlease check the FAQ for possible reasons why this error might occur.")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
.show()
Expand Down
145 changes: 0 additions & 145 deletions app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.java

This file was deleted.

142 changes: 142 additions & 0 deletions app/src/main/java/com/zeapo/pwdstore/git/GitAsyncTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package com.zeapo.pwdstore.git

import android.app.Activity
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.PasswordStore
import com.zeapo.pwdstore.R
import net.schmizz.sshj.userauth.UserAuthException
import org.eclipse.jgit.api.CommitCommand
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.api.PullCommand
import org.eclipse.jgit.api.PushCommand
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.api.StatusCommand
import org.eclipse.jgit.transport.RemoteRefUpdate
import java.io.IOException
import java.lang.ref.WeakReference


class GitAsyncTask(
activity: Activity,
private val refreshListOnEnd: Boolean,
private val operation: GitOperation,
private val finishWithResultOnEnd: Intent?) : AsyncTask<GitCommand<*>, Int, GitAsyncTask.Result>() {

private val activityWeakReference: WeakReference<Activity> = WeakReference(activity)
private val activity: Activity?
get() = activityWeakReference.get()
private val context: Context = activity.applicationContext
private val dialog = ProgressDialog(activity)

sealed class Result {
object Ok : Result()
data class Err(val err: Exception) : Result()
}

override fun onPreExecute() {
dialog.run {
setMessage(activity!!.resources.getString(R.string.running_dialog_text))
setCancelable(false)
show()
}
}

override fun doInBackground(vararg commands: GitCommand<*>): Result? {
var nbChanges: Int? = null
for (command in commands) {
try {
if (command is StatusCommand) {
// in case we have changes, we want to keep track of it
val status = command.call()
nbChanges = status.changed.size + status.missing.size
} else if (command is CommitCommand) {
// the previous status will eventually be used to avoid a commit
if (nbChanges == null || nbChanges > 0) command.call()
} else if (command is PullCommand) {
val result = command.call()
val rr = result.rebaseResult
if (rr.status === RebaseResult.Status.STOPPED) {
return Result.Err(IOException(context.getString(R.string
.git_pull_fail_error)))
}
} else if (command is PushCommand) {
for (result in command.call()) {
// Code imported (modified) from Gerrit PushOp, license Apache v2
for (rru in result.remoteUpdates) {
val error = when (rru.status) {
RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD ->
context.getString(R.string.git_push_nff_error)
RemoteRefUpdate.Status.REJECTED_NODELETE,
RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
RemoteRefUpdate.Status.NON_EXISTING,
RemoteRefUpdate.Status.NOT_ATTEMPTED ->
(activity!!.getString(R.string.git_push_generic_error) + rru.status.name)
RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> {
if
("non-fast-forward" == rru.message) {
context.getString(R.string.git_push_other_error)
} else {
(context.getString(R.string.git_push_generic_error)
+ rru.message)
}
}
else -> null

}
if (error != null)
Result.Err(IOException(error))
}
}
} else {
command.call()
}
} catch (e: Exception) {
return Result.Err(e)
}
}
return Result.Ok
}

private fun rootCauseException(e: Exception): Exception {
var rootCause = e
// JGit's TransportException hides the more helpful SSHJ exceptions.
// Also, SSHJ's UserAuthException about exhausting available authentication methods hides
// more useful exceptions.
while ((rootCause is org.eclipse.jgit.errors.TransportException ||
rootCause is org.eclipse.jgit.api.errors.TransportException ||
(rootCause is UserAuthException &&
rootCause.message == "Exhausted available authentication methods"))) {
rootCause = rootCause.cause as? Exception ?: break
}
return rootCause
}

override fun onPostExecute(maybeResult: Result?) {
dialog.dismiss()
when (val result = maybeResult ?: Result.Err(IOException("Unexpected error"))) {
is Result.Err -> {
e(result.err)
operation.onError(rootCauseException(result.err))
}
is Result.Ok -> {
operation.onSuccess()
if (finishWithResultOnEnd != null) {
activity?.setResult(Activity.RESULT_OK, finishWithResultOnEnd)
activity?.finish()
}
if (refreshListOnEnd) {
(activity as? PasswordStore)?.resetPasswordList()
}
}
}
}

}
Loading

0 comments on commit 3546f4e

Please sign in to comment.