From 1780d24955326b00f87085d1716a0dd124ee23cc Mon Sep 17 00:00:00 2001 From: Joshua Soberg Date: Wed, 27 Jan 2021 22:00:32 -0500 Subject: [PATCH] #635 - First pass at launching the background task when authenticating Began disabling the password input and unlock buttons while the task is running, for some UI feedback. Right now it's just waiting for the task to finish if the activity gets paused while it's running; this isn't great since there's potential there to lock the main thread, but the task is so short-lived that it may not be an issue. --- .../Activities/AuthenticateActivity.java | 111 +++++++++--------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java index fdd7dac2..5a8c7031 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/AuthenticateActivity.java @@ -28,10 +28,11 @@ import android.os.Bundle; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; +import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import android.text.InputType; import android.text.method.PasswordTransformationMethod; -import android.util.Base64; +import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.ViewStub; @@ -42,21 +43,20 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.DigestUtils; import org.shadowice.flocke.andotp.R; +import org.shadowice.flocke.andotp.Tasks.AuthenticationTask; +import org.shadowice.flocke.andotp.Tasks.AuthenticationTask.Callback; +import org.shadowice.flocke.andotp.Tasks.AuthenticationTask.Result; import org.shadowice.flocke.andotp.Utilities.Constants; -import org.shadowice.flocke.andotp.Utilities.EncryptionHelper; -import org.shadowice.flocke.andotp.Utilities.EncryptionHelper.PBKDF2Credentials; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; +import java.util.concurrent.ExecutionException; import static org.shadowice.flocke.andotp.Utilities.Constants.AuthMethod; public class AuthenticateActivity extends ThemedActivity - implements EditText.OnEditorActionListener, View.OnClickListener { + implements EditText.OnEditorActionListener, View.OnClickListener { + + private static final String KEY_WAS_TASK_ACTIVE = "AuthenticateActivity.WasTaskActive"; private AuthMethod authMethod; private String newEncryption = ""; @@ -64,6 +64,9 @@ public class AuthenticateActivity extends ThemedActivity private boolean isAuthUpgrade = false; private TextInputEditText passwordInput; + private Button unlockButton; + + private AuthenticationTask activeTask; @Override protected void onCreate(Bundle savedInstanceState) { @@ -146,78 +149,76 @@ public class AuthenticateActivity extends ThemedActivity } private void initUnlockButtonView(View v) { - Button unlockButton = v.findViewById(R.id.buttonUnlock); + unlockButton = v.findViewById(R.id.buttonUnlock); unlockButton.setOnClickListener(this); } @Override public void onClick(View view) { - checkPassword(passwordInput.getText().toString()); + startAuthTask(passwordInput.getText().toString()); } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { - checkPassword(v.getText().toString()); + startAuthTask(v.getText().toString()); return true; } return false; } - public void checkPassword(String plainPassword) { - if (!isAuthUpgrade) { - try { - PBKDF2Credentials credentials = EncryptionHelper.generatePBKDF2Credentials(plainPassword, settings.getSalt(), settings.getIterations()); - byte[] passwordArray = Base64.decode(existingAuthCredentials, Base64.URL_SAFE); - - if (Arrays.equals(passwordArray, credentials.password)) { - finishWithResult(true, credentials.key); - } else { - finishWithResult(false, null); - } - } catch (NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) { - e.printStackTrace(); - finishWithResult(false, null); - } - } else { - String hashedPassword = new String(Hex.encodeHex(DigestUtils.sha256(plainPassword))); - - if (hashedPassword.equals(existingAuthCredentials)) { - byte[] key = settings.setAuthCredentials(plainPassword); - - if (key == null) - Toast.makeText(this, R.string.settings_toast_auth_upgrade_failed, Toast.LENGTH_LONG).show(); - - if (authMethod == AuthMethod.PASSWORD) - settings.removeAuthPasswordHash(); - else if (authMethod == AuthMethod.PIN) - settings.removeAuthPINHash(); - - finishWithResult(true, key); - } else { - finishWithResult(false, null); - } - } + private void startAuthTask(String plainPassword) { + passwordInput.setEnabled(false); + unlockButton.setEnabled(false); + Callback callback = this::handleResult; + activeTask = new AuthenticationTask(this, callback, isAuthUpgrade, existingAuthCredentials, plainPassword); + activeTask.execute(); } - // End with a result - public void finishWithResult(boolean success, byte[] key) { - Intent data = new Intent(); + private void handleResult(Result result) { + activeTask = null; + if (result.authUpgradeFailed) { + Toast.makeText(this, R.string.settings_toast_auth_upgrade_failed, Toast.LENGTH_LONG).show(); + } + finishWithResult(result.encryptionKey != null, result.encryptionKey); + } - if (newEncryption != null && ! newEncryption.isEmpty()) + private void finishWithResult(boolean success, byte[] encryptionKey) { + Intent data = new Intent(); + if (newEncryption != null && !newEncryption.isEmpty()) data.putExtra(Constants.EXTRA_AUTH_NEW_ENCRYPTION, newEncryption); - if (key != null) - data.putExtra(Constants.EXTRA_AUTH_PASSWORD_KEY, key); + if (encryptionKey != null) + data.putExtra(Constants.EXTRA_AUTH_PASSWORD_KEY, encryptionKey); if (success) setResult(RESULT_OK, data); - finish(); } - // Go back to the main activity + @Override + protected void onPause() { + super.onPause(); + completeTaskIfActive(); + } + + /** @return true if the task was active and was completed, false otherwise. */ + private boolean completeTaskIfActive() { + try { + // This will cause the main thread to wait, but it'll ensure that the task completes. + if (activeTask != null) { + handleResult(activeTask.get()); + return true; + } + } catch (ExecutionException | InterruptedException e) { + Log.e("AuthenticateActivity", "Could not finish active authentication task", e); + } + return false; + } + @Override public void onBackPressed() { - finishWithResult(false, null); + if (!completeTaskIfActive()) { + finishWithResult(false, null); + } super.onBackPressed(); } }