Add setting to save OpenKeychain auth keyid (#554)

* Add setting to save OpenKeychain auth keyid

* Hide pref not disable

Co-Authored-By: Reagan Sanders <vexofp@gmail.com>
Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
This commit is contained in:
Harsh Shandilya 2019-10-27 00:14:42 +05:30 committed by GitHub
parent 2fcec8685b
commit cdf45bc323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 67 additions and 15 deletions

View file

@ -66,6 +66,7 @@ class UserPreference : AppCompatActivity() {
// Git preferences // Git preferences
val gitServerPreference = findPreference<Preference>("git_server_info") val gitServerPreference = findPreference<Preference>("git_server_info")
val openkeystoreIdPreference = findPreference<Preference>("ssh_openkeystore_clear_keyid")
val gitConfigPreference = findPreference<Preference>("git_config") val gitConfigPreference = findPreference<Preference>("git_config")
val sshKeyPreference = findPreference<Preference>("ssh_key") val sshKeyPreference = findPreference<Preference>("ssh_key")
val sshKeygenPreference = findPreference<Preference>("ssh_keygen") val sshKeygenPreference = findPreference<Preference>("ssh_keygen")
@ -114,6 +115,7 @@ class UserPreference : AppCompatActivity() {
OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s)) OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
} }
} }
openkeystoreIdPreference?.isVisible = sharedPreferences.getString("ssh_openkeystore_keyid", null)?.isNotEmpty() ?: false
// see if the autofill service is enabled and check the preference accordingly // see if the autofill service is enabled and check the preference accordingly
autoFillEnablePreference?.isChecked = callingActivity.isServiceEnabled autoFillEnablePreference?.isChecked = callingActivity.isServiceEnabled
@ -156,6 +158,12 @@ class UserPreference : AppCompatActivity() {
true true
} }
openkeystoreIdPreference?.onPreferenceClickListener = ClickListener {
sharedPreferences.edit().putString("ssh_openkeystore_keyid", null).apply()
it.isVisible = false
true
}
gitServerPreference?.onPreferenceClickListener = ClickListener { gitServerPreference?.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, GitActivity::class.java) val intent = Intent(callingActivity, GitActivity::class.java)
intent.putExtra("Operation", GitActivity.EDIT_SERVER) intent.putExtra("Operation", GitActivity.EDIT_SERVER)

View file

@ -64,6 +64,7 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
} }
override fun onError(errorMessage: String) { override fun onError(errorMessage: String) {
super.onError(errorMessage)
MaterialAlertDialogBuilder(callingActivity) MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the clone operation, " + .setMessage("Error occured during the clone operation, " +

View file

@ -613,7 +613,7 @@ open class GitActivity : AppCompatActivity() {
} }
} }
override fun onActivityResult( public override fun onActivityResult(
requestCode: Int, requestCode: Int,
resultCode: Int, resultCode: Int,
data: Intent? data: Intent?
@ -625,9 +625,17 @@ open class GitActivity : AppCompatActivity() {
// background thread - the actual signing of the SSH challenge. We pass through the // background thread - the actual signing of the SSH challenge. We pass through the
// completed signature to the ApiIdentity, which will be blocked in the other thread // completed signature to the ApiIdentity, which will be blocked in the other thread
// waiting for it. // waiting for it.
if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null) if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null) {
identity!!.postSignature(data) identity!!.postSignature(data)
// If the signature failed (usually because it was cancelled), reset state
if (data == null) {
identity = null
identityBuilder = null
}
return
}
if (resultCode == AppCompatActivity.RESULT_CANCELED) { if (resultCode == AppCompatActivity.RESULT_CANCELED) {
setResult(AppCompatActivity.RESULT_CANCELED) setResult(AppCompatActivity.RESULT_CANCELED)
finish() finish()

View file

@ -235,14 +235,11 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
* Action to execute on error * Action to execute on error
*/ */
open fun onError(errorMessage: String) { open fun onError(errorMessage: String) {
MaterialAlertDialogBuilder(callingActivity) if (SshSessionFactory.getInstance() is SshApiSessionFactory) {
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) // Clear stored key id from settings on auth failure
.setMessage(callingActivity.resources.getString(R.string.jgit_error_dialog_text) + errorMessage) PreferenceManager.getDefaultSharedPreferences(callingActivity.applicationContext)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> .edit().putString("ssh_openkeystore_keyid", null).apply()
callingActivity.setResult(Activity.RESULT_CANCELED)
callingActivity.finish()
} }
.show()
} }
/** /**

View file

@ -38,6 +38,7 @@ class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
} }
override fun onError(errorMessage: String) { override fun onError(errorMessage: String) {
super.onError(errorMessage)
MaterialAlertDialogBuilder(callingActivity) MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the pull operation, " + .setMessage("Error occured during the pull operation, " +

View file

@ -39,6 +39,7 @@ class PushOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
override fun onError(errorMessage: String) { override fun onError(errorMessage: String) {
// TODO handle the "Nothing to push" case // TODO handle the "Nothing to push" case
super.onError(errorMessage)
MaterialAlertDialogBuilder(callingActivity) MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage) .setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage)

View file

@ -44,6 +44,7 @@ class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOper
} }
override fun onError(errorMessage: String) { override fun onError(errorMessage: String) {
super.onError(errorMessage)
MaterialAlertDialogBuilder(callingActivity) MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the sync operation, " + .setMessage("Error occured during the sync operation, " +

View file

@ -52,6 +52,7 @@ class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
} }
override fun onError(errorMessage: String) { override fun onError(errorMessage: String) {
super.onError(errorMessage)
MaterialAlertDialogBuilder(callingActivity) MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title)) .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the sync operation, " + .setMessage("Error occured during the sync operation, " +

View file

@ -8,7 +8,9 @@ import android.app.Activity;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender; import android.content.IntentSender;
import android.content.SharedPreferences;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jcraft.jsch.Identity; import com.jcraft.jsch.Identity;
import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSch;
@ -16,6 +18,7 @@ import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session; import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo; import com.jcraft.jsch.UserInfo;
import com.zeapo.pwdstore.R; import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.git.GitActivity;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.errors.UnsupportedCredentialItem;
@ -104,7 +107,8 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
private SshAuthenticationApi api; private SshAuthenticationApi api;
private String keyId, description, alg; private String keyId, description, alg;
private byte[] publicKey; private byte[] publicKey;
private Activity callingActivity; private GitActivity callingActivity;
private SharedPreferences settings;
/** /**
* Construct a new IdentityBuilder * Construct a new IdentityBuilder
@ -112,7 +116,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
* @param callingActivity Activity that will be used to launch pending intents and that will * @param callingActivity Activity that will be used to launch pending intents and that will
* receive and handle the results. * receive and handle the results.
*/ */
public IdentityBuilder(Activity callingActivity) { public IdentityBuilder(GitActivity callingActivity) {
this.callingActivity = callingActivity; this.callingActivity = callingActivity;
List<String> providers = List<String> providers =
@ -124,6 +128,11 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
// TODO: Handle multiple available providers? Are there actually any in practice beyond // TODO: Handle multiple available providers? Are there actually any in practice beyond
// OpenKeychain? // OpenKeychain?
connection = new SshAuthenticationConnection(callingActivity, providers.get(0)); connection = new SshAuthenticationConnection(callingActivity, providers.get(0));
settings =
PreferenceManager.getDefaultSharedPreferences(
callingActivity.getApplicationContext());
keyId = settings.getString("ssh_openkeystore_keyid", null);
} }
/** Free any resources associated with this IdentityBuilder */ /** Free any resources associated with this IdentityBuilder */
@ -146,7 +155,24 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
case SshAuthenticationApi.RESULT_CODE_ERROR: case SshAuthenticationApi.RESULT_CODE_ERROR:
SshAuthenticationApiError error = SshAuthenticationApiError error =
result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR); result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
// On an OpenKeychain SSH API error, clear out the stored keyid
settings.edit().putString("ssh_openkeystore_keyid", null).apply();
switch (error.getError()) {
// If the problem was just a bad keyid, reset to allow them to choose a
// different one
case (SshAuthenticationApiError.NO_SUCH_KEY):
case (SshAuthenticationApiError.NO_AUTH_KEY):
keyId = null;
publicKey = null;
description = null;
alg = null;
return executeApi(new KeySelectionRequest(), requestCode);
// Other errors are fatal
default:
throw new RuntimeException(error.getMessage()); throw new RuntimeException(error.getMessage());
}
case SshAuthenticationApi.RESULT_CODE_SUCCESS: case SshAuthenticationApi.RESULT_CODE_SUCCESS:
break; break;
case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@ -181,6 +207,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
if (intent.hasExtra(SshAuthenticationApi.EXTRA_KEY_ID)) { if (intent.hasExtra(SshAuthenticationApi.EXTRA_KEY_ID)) {
keyId = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_ID); keyId = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_ID);
description = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_DESCRIPTION); description = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_DESCRIPTION);
settings.edit().putString("ssh_openkeystore_keyid", keyId).apply();
} }
if (intent.hasExtra(SshAuthenticationApi.EXTRA_SSH_PUBLIC_KEY)) { if (intent.hasExtra(SshAuthenticationApi.EXTRA_SSH_PUBLIC_KEY)) {
@ -209,7 +236,8 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
// We can immediately try the next phase without needing to post // We can immediately try the next phase without needing to post
// back // back
// though onActivityResult // though onActivityResult
tryBuild(requestCode); callingActivity.onActivityResult(
requestCode, Activity.RESULT_OK, null);
} }
@Override @Override
@ -342,7 +370,9 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
*/ */
public void postSignature(Intent data) { public void postSignature(Intent data) {
try { try {
if (data != null) {
signature = handleSignResult(data); signature = handleSignResult(data);
}
} finally { } finally {
if (latch != null) latch.countDown(); if (latch != null) latch.countDown();
} }

View file

@ -271,4 +271,5 @@
<string name="biometric_auth_title">Enable biometric authentication</string> <string name="biometric_auth_title">Enable biometric authentication</string>
<string name="biometric_auth_summary">When enabled, Password Store will prompt you for your fingerprint when launching the app</string> <string name="biometric_auth_summary">When enabled, Password Store will prompt you for your fingerprint when launching the app</string>
<string name="biometric_auth_summary_error">Fingerprint hardware not accessible or missing</string> <string name="biometric_auth_summary_error">Fingerprint hardware not accessible or missing</string>
<string name="ssh_openkeystore_clear_keyid">Clear remembered OpenKeystore SSH Key ID</string>
</resources> </resources>

View file

@ -19,6 +19,9 @@
<androidx.preference.Preference <androidx.preference.Preference
android:key="hotp_remember_clear_choice" android:key="hotp_remember_clear_choice"
android:title="@string/hotp_remember_clear_choice" /> android:title="@string/hotp_remember_clear_choice" />
<androidx.preference.Preference
android:key="ssh_openkeystore_clear_keyid"
android:title="@string/ssh_openkeystore_clear_keyid" />
<androidx.preference.Preference <androidx.preference.Preference
android:key="ssh_see_key" android:key="ssh_see_key"
android:title="@string/pref_ssh_see_key_title" /> android:title="@string/pref_ssh_see_key_title" />