From f272e4dde2c05de64681a0e089387ef9c54bd7c7 Mon Sep 17 00:00:00 2001 From: vexofp Date: Fri, 5 Apr 2019 18:14:38 -0400 Subject: Authentication using OpenKeystore SSH API (#486) * Implemented OpenKeystore SSH API as a new authentication option * Fix formatting problems Signed-off-by: Harsh Shandilya * Addressed review comments. Removed leftover debugging code. Wrapped excessively long lines. Added missing new parameter to Javadoc. * Merge remote-tracking branch 'upstream/master' into gpg-ssh-key --- .../java/com/zeapo/pwdstore/git/GitActivity.java | 150 +++++++++++++-------- 1 file changed, 95 insertions(+), 55 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java') diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java index ca451247..e945c973 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java @@ -17,11 +17,15 @@ import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; + import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; + import com.zeapo.pwdstore.R; import com.zeapo.pwdstore.UserPreference; +import com.zeapo.pwdstore.git.config.SshApiSessionFactory; import com.zeapo.pwdstore.utils.PasswordRepository; + import org.apache.commons.io.FileUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.RebaseCommand; @@ -53,6 +57,8 @@ public class GitActivity extends AppCompatActivity { private File localDir; private String hostname; private SharedPreferences settings; + private SshApiSessionFactory.IdentityBuilder identityBuilder; + private SshApiSessionFactory.ApiIdentity identity; @Override protected void onCreate(Bundle savedInstanceState) { @@ -117,8 +123,10 @@ public class GitActivity extends AppCompatActivity { connection_mode_spinner.setEnabled(true); // however, if we have some saved that, that's more important! - if (connectionMode.equals("ssh-key")) { + if (connectionMode.equalsIgnoreCase("ssh-key")) { connection_mode_spinner.setSelection(0); + } else if (connectionMode.equalsIgnoreCase("OpenKeychain")) { + connection_mode_spinner.setSelection(2); } else { connection_mode_spinner.setSelection(1); } @@ -370,6 +378,16 @@ public class GitActivity extends AppCompatActivity { updateURI(); } + @Override + protected void onDestroy() { + // Do not leak the service connection + if (identityBuilder != null) { + identityBuilder.close(); + identityBuilder = null; + } + super.onDestroy(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -556,16 +574,7 @@ public class GitActivity extends AppCompatActivity { (dialog, id) -> { try { FileUtils.deleteDirectory(localDir); - try { - new CloneOperation(localDir, activity) - .setCommand(hostname) - .executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key")); - } catch (Exception e) { - //This is what happens when jgit fails :( - //TODO Handle the diffent cases of exceptions - e.printStackTrace(); - new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show(); - } + launchGitOperation(REQUEST_CLONE); } catch (IOException e) { //TODO Handle the exception correctly if we are unable to delete the directory... e.printStackTrace(); @@ -590,15 +599,13 @@ public class GitActivity extends AppCompatActivity { new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show(); } } - new CloneOperation(localDir, activity) - .setCommand(hostname) - .executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key")); } catch (Exception e) { //This is what happens when jgit fails :( //TODO Handle the diffent cases of exceptions e.printStackTrace(); new AlertDialog.Builder(this).setMessage(e.getMessage()).show(); } + launchGitOperation(REQUEST_CLONE); } } @@ -627,47 +634,45 @@ public class GitActivity extends AppCompatActivity { else { // check that the remote origin is here, else add it PasswordRepository.addRemote("origin", hostname, false); - GitOperation op; - - switch (operation) { - case REQUEST_PULL: - op = new PullOperation(localDir, activity).setCommand(); - break; - case REQUEST_PUSH: - op = new PushOperation(localDir, activity).setCommand(); - break; - case REQUEST_SYNC: - op = new SyncOperation(localDir, activity).setCommands(); - break; - default: - Log.e(TAG, "Sync operation not recognized : " + operation); - return; - } - - try { - op.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key")); - } catch (Exception e) { - e.printStackTrace(); - } + launchGitOperation(operation); } } - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - if (resultCode == RESULT_CANCELED) { - setResult(RESULT_CANCELED); - finish(); - return; - } + /** + * Attempt to launch the requested GIT operation. Depending on the configured auth, it may not + * be possible to launch the operation immediately. In that case, this function may launch an + * intermediate activity instead, which will gather necessary information and post it back via + * onActivityResult, which will then re-call this function. This may happen multiple times, + * until either an error is encountered or the operation is successfully launched. + * + * @param operation The type of GIT operation to launch + */ + protected void launchGitOperation(int operation) { + GitOperation op; + + try { + + // Before launching the operation with OpenKeychain auth, we need to issue several requests + // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents, + // we just need to keep calling it until it returns a completed ApiIdentity. + if (connectionMode.equalsIgnoreCase("OpenKeychain") && identity == null) { + // Lazy initialization of the IdentityBuilder + if (identityBuilder == null) { + identityBuilder = new SshApiSessionFactory.IdentityBuilder(this); + } - if (resultCode == RESULT_OK) { - GitOperation op; + // Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure + // that onActivityResult is called with operation again, which will re-invoke us here + identity = identityBuilder.tryBuild(operation); + if (identity == null) + return; + } - switch (requestCode) { + switch (operation) { case REQUEST_CLONE: - setResult(RESULT_OK); - finish(); - return; + op = new CloneOperation(localDir, activity).setCommand(hostname); + break; + case REQUEST_PULL: op = new PullOperation(localDir, activity).setCommand(); break; @@ -676,22 +681,57 @@ public class GitActivity extends AppCompatActivity { op = new PushOperation(localDir, activity).setCommand(); break; + case REQUEST_SYNC: + op = new SyncOperation(localDir, activity).setCommands(); + break; + case GitOperation.GET_SSH_KEY_FROM_CLONE: op = new CloneOperation(localDir, activity).setCommand(hostname); break; + + case SshApiSessionFactory.POST_SIGNATURE: + return; + default: - Log.e(TAG, "Operation not recognized : " + resultCode); + Log.e(TAG, "Operation not recognized : " + operation); setResult(RESULT_CANCELED); finish(); return; } - try { - op.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key")); - } catch (Exception e) { - e.printStackTrace(); - } + op.executeAfterAuthentication(connectionMode, + settings.getString("git_remote_username", "git"), + new File(getFilesDir() + "/.ssh_key"), + identity); + } catch (Exception e) { + e.printStackTrace(); + new AlertDialog.Builder(this).setMessage(e.getMessage()).show(); + } + } + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + + // In addition to the pre-operation-launch series of intents for OpenKeychain auth + // that will pass through here and back to launchGitOperation, there is one + // synchronous operation that happens /after/ the operation has been launched in 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 + // waiting for it. + if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null) + identity.postSignature(data); + + if (resultCode == RESULT_CANCELED) { + setResult(RESULT_CANCELED); + finish(); + } else if (resultCode == RESULT_OK) { + // If an operation has been re-queued via this mechanism, let the + // IdentityBuilder attempt to extract some updated state from the intent before + // trying to re-launch the operation. + if (identityBuilder != null) { + identityBuilder.consume(data); + } + launchGitOperation(requestCode); } } -- cgit v1.2.3