From 6e4b58c194610df09126dfede6e3a1737973091d Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:25:11 +0200 Subject: [PATCH 1/5] Rename auth failure delay constant to DELAY_AFTER_AUTH_FAILED --- .../main/java/app/notesr/activity/security/AuthHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/notesr/activity/security/AuthHandler.java b/app/src/main/java/app/notesr/activity/security/AuthHandler.java index 97da88fe..0ee13b7b 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthHandler.java +++ b/app/src/main/java/app/notesr/activity/security/AuthHandler.java @@ -40,7 +40,7 @@ @RequiredArgsConstructor public final class AuthHandler { private static final int MAX_ATTEMPTS = 3; - private static final int ON_WRONG_PASSWORD_DELAY_MS = 1500; + private static final int DELAY_AFTER_AUTH_FAILED = 1500; private final AuthActivity activity; private final AppSecurityService appSecurityService; @@ -195,7 +195,7 @@ private void onAuthenticationFailed() { KeyRecoveryActivity.class)); } else { try { - Thread.sleep(ON_WRONG_PASSWORD_DELAY_MS); + Thread.sleep(DELAY_AFTER_AUTH_FAILED); } catch (InterruptedException e) { throw new RuntimeException(e); } From 512320235bbd937ba7b8f64826f81e7b805622d1 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:25:40 +0200 Subject: [PATCH 2/5] Rename `attempts` field to `authAttempts` in `AuthHandler` --- .../java/app/notesr/activity/security/AuthHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/notesr/activity/security/AuthHandler.java b/app/src/main/java/app/notesr/activity/security/AuthHandler.java index 0ee13b7b..2ce5760b 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthHandler.java +++ b/app/src/main/java/app/notesr/activity/security/AuthHandler.java @@ -47,7 +47,7 @@ public final class AuthHandler { private final SecretsRotationService secretsRotationService; private final SecureStringBuilder passwordBuilder; - private int attempts = MAX_ATTEMPTS; + private int authAttempts = MAX_ATTEMPTS; private char[] createdPassword; public void authenticate() { @@ -181,9 +181,9 @@ private void onAuthenticationSuccessful() { } private void onAuthenticationFailed() { - attempts--; + authAttempts--; - if (attempts == 0) { + if (authAttempts == 0) { try { appSecurityService.blockApp(); } catch (AppSecurityException e) { @@ -202,7 +202,7 @@ private void onAuthenticationFailed() { showToastMessage(String.format( activity.getString(R.string.wrong_code_you_have_n_attempts), - attempts)); + authAttempts)); } resetPassword(); From 44f1c55e514c68ece34b67a9b952dd46d0b3e336 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 15:32:14 +0200 Subject: [PATCH 3/5] Add activity finish call after showing blocked message in AuthHandler --- app/src/main/java/app/notesr/activity/security/AuthHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/app/notesr/activity/security/AuthHandler.java b/app/src/main/java/app/notesr/activity/security/AuthHandler.java index 2ce5760b..aed4db36 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthHandler.java +++ b/app/src/main/java/app/notesr/activity/security/AuthHandler.java @@ -193,6 +193,7 @@ private void onAuthenticationFailed() { showToastMessage(R.string.blocked); activity.startActivity(new Intent(activity.getApplicationContext(), KeyRecoveryActivity.class)); + activity.finish(); } else { try { Thread.sleep(DELAY_AFTER_AUTH_FAILED); From 5eccfb9a8cc58a65c0e88a4b43851193712047a7 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 20:10:24 +0200 Subject: [PATCH 4/5] Refactor KeyRecoveryActivity to improve key application logic --- .../security/KeyRecoveryActivity.java | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java index 85d349fc..64a176bf 100644 --- a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java +++ b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java @@ -12,7 +12,6 @@ import static app.notesr.core.util.CharUtils.charsToBytes; import static app.notesr.core.util.KeyUtils.getKeyBytesFromKeyHex; -import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.Editable; @@ -30,10 +29,8 @@ import app.notesr.service.security.AppSecurityException; import app.notesr.service.security.AppSecurityService; -import java.io.IOException; import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Objects; @@ -41,6 +38,7 @@ public final class KeyRecoveryActivity extends ActivityBase { private static final String TAG = KeyRecoveryActivity.class.toString(); private AppSecurityService appSecurityService; + private EditText hexKeyField; @Override protected void onCreate(Bundle savedInstanceState) { @@ -53,13 +51,13 @@ protected void onCreate(Bundle savedInstanceState) { ActionBar actionBar = getSupportActionBar(); Objects.requireNonNull(actionBar).setTitle(getString(R.string.key_recovery)); - EditText hexKeyField = findViewById(R.id.importRecoveryKeyField); + hexKeyField = findViewById(R.id.importRecoveryKeyField); Button applyButton = findViewById(R.id.applyRecoveryKeyButton); disableBackButton(this); hexKeyField.setImeOptions(IME_FLAG_NO_PERSONALIZED_LEARNING); - applyButton.setOnClickListener(applyButtonOnClick(hexKeyField)); + applyButton.setOnClickListener(getApplyButtonOnClickListener()); } @Override @@ -67,7 +65,17 @@ protected boolean requiresSession() { return false; } - private View.OnClickListener applyButtonOnClick(EditText hexKeyField) { + @Override + public void finish() { + if (hexKeyField != null) { + hexKeyField.getText().replace(0, hexKeyField.getText().length(), ""); + hexKeyField.setText(""); + } + + super.finish(); + } + + private View.OnClickListener getApplyButtonOnClickListener() { return view -> { Editable hexKeyEditable = hexKeyField.getText(); int hexKeyLength = hexKeyEditable.length(); @@ -77,56 +85,49 @@ private View.OnClickListener applyButtonOnClick(EditText hexKeyField) { hexKeyEditable.getChars(0, hexKeyLength, hexKey, 0); try { - apply(hexKeyField, hexKey); + if (isMatch(hexKey)) { + proceedKeyMatch(hexKey); + } else { + proceedKeyMismatch(); + } } catch (IllegalArgumentException e) { Log.e(TAG, "Invalid key", e); showToastMessage(this, getString(R.string.invalid_key), Toast.LENGTH_SHORT); - } catch (CharacterCodingException e) { - throw new RuntimeException(e); - } catch (IOException | NoSuchAlgorithmException e) { + } catch (AppSecurityException | CharacterCodingException e) { Log.e(TAG, e.toString()); throw new RuntimeException(e); + } finally { + Arrays.fill(hexKey, '\0'); } } }; } - private void apply(EditText hexKeyField, char[] hexKey) - throws IOException, NoSuchAlgorithmException { - - char[] hexKeyCopy = Arrays.copyOf(hexKey, hexKey.length); - byte[] keyBytes = getKeyBytesFromKeyHex(hexKeyCopy); + private boolean isMatch(char[] hexKey) { + byte[] keyBytes = getKeyBytesFromKeyHex(Arrays.copyOf(hexKey, hexKey.length)); + boolean isMatch = appSecurityService.isKeyMatchingWithStored(keyBytes); - Context context = getApplicationContext(); - - try { - if (appSecurityService.isKeyMatchingWithStored(keyBytes)) { - byte[] hexKeyBytes = charsToBytes(hexKey, StandardCharsets.UTF_8); - SecretCache.put(AuthActivity.CACHE_KEY_HEX_KEY, hexKeyBytes); + Arrays.fill(keyBytes, (byte) 0); + return isMatch; + } - // The hex key has already been wiped by charsToBytes - wipeSecretData(keyBytes, hexKeyField); + private void proceedKeyMatch(char[] hexKey) throws CharacterCodingException { + byte[] hexKeyBytes = charsToBytes(Arrays.copyOf(hexKey, hexKey.length), + StandardCharsets.UTF_8); + SecretCache.put(AuthActivity.CACHE_KEY_HEX_KEY, hexKeyBytes); - var targetMode = AuthActivity.Mode.KEY_RECOVERY; - var authActivityIntent = new Intent(context, AuthActivity.class) - .putExtra(AuthActivity.EXTRA_MODE, targetMode.toString()); + var targetMode = AuthActivity.Mode.KEY_RECOVERY; + var authActivityIntent = new Intent(getApplicationContext(), AuthActivity.class) + .putExtra(AuthActivity.EXTRA_MODE, targetMode.toString()); - startActivity(authActivityIntent); - finish(); - } else { - showToastMessage(this, - getString(R.string.wrong_key), - Toast.LENGTH_SHORT); - } - } catch (AppSecurityException e) { - throw new RuntimeException(e); - } + startActivity(authActivityIntent); + finish(); } - private void wipeSecretData(byte[] keyBytes, EditText keyField) { - Arrays.fill(keyBytes, (byte) 0); - keyField.getText().replace(0, keyField.getText().length(), ""); - keyField.setText(""); + private void proceedKeyMismatch() { + showToastMessage(this, + getString(R.string.wrong_key), + Toast.LENGTH_SHORT); } } From 11360e6f2fe7814718edeade37695773491cdd87 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Tue, 4 Aug 2026 10:03:17 +0200 Subject: [PATCH 5/5] Use simple class name for TAG in KeyRecoveryActivity --- .../java/app/notesr/activity/security/KeyRecoveryActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java index 64a176bf..3818ddd0 100644 --- a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java +++ b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java @@ -35,7 +35,7 @@ import java.util.Objects; public final class KeyRecoveryActivity extends ActivityBase { - private static final String TAG = KeyRecoveryActivity.class.toString(); + private static final String TAG = KeyRecoveryActivity.class.getSimpleName(); private AppSecurityService appSecurityService; private EditText hexKeyField;