From aacc9b4b8bfcc93bdc9194ea17281fb636178706 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:53:02 +0200 Subject: [PATCH 01/30] Move secrets update logic from `crypto.update` to `rotation` package --- app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/app/notesr/activity/FsaResolver.java | 2 +- .../notesr/activity/security/KeySetupCompletionHandler.java | 2 +- .../app/notesr/activity/security/ReEncryptionActivity.java | 4 ++-- .../activity/security/ReEncryptionBroadcastReceiver.java | 2 +- .../security/{crypto/update => rotation}/DatabaseManager.java | 2 +- .../{crypto/update => rotation}/DatabaseManagerImpl.java | 2 +- .../update => rotation}/SecretsUpdateAndroidService.java | 2 +- .../SecretsUpdateAndroidServiceStarter.java | 4 ++-- .../update => rotation}/SecretsUpdateFailedException.java | 2 +- .../{crypto/update => rotation}/SecretsUpdateService.java | 2 +- .../{crypto/update => rotation}/SecretsUpdateState.java | 2 +- .../{crypto/update => rotation}/SecretsUpdateStateHolder.java | 2 +- .../{crypto/update => rotation}/SecretsUpdateStatus.java | 2 +- .../SecretsUpdateAndroidServiceStarterTest.java | 2 +- .../update => rotation}/SecretsUpdateAndroidServiceTest.java | 2 +- .../{crypto/update => rotation}/SecretsUpdateServiceTest.java | 2 +- 17 files changed, 19 insertions(+), 19 deletions(-) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/DatabaseManager.java (89%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/DatabaseManagerImpl.java (96%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateAndroidService.java (99%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateAndroidServiceStarter.java (95%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateFailedException.java (87%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateService.java (99%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateState.java (95%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateStateHolder.java (93%) rename service/src/main/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateStatus.java (92%) rename service/src/test/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateAndroidServiceStarterTest.java (99%) rename service/src/test/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateAndroidServiceTest.java (99%) rename service/src/test/java/app/notesr/service/security/{crypto/update => rotation}/SecretsUpdateServiceTest.java (99%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dad8575d..0eb3d4d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -117,7 +117,7 @@ android:stopWithTask="false" android:foregroundServiceType="dataSync" /> diff --git a/app/src/main/java/app/notesr/activity/FsaResolver.java b/app/src/main/java/app/notesr/activity/FsaResolver.java index 0f0697de..8beab245 100644 --- a/app/src/main/java/app/notesr/activity/FsaResolver.java +++ b/app/src/main/java/app/notesr/activity/FsaResolver.java @@ -9,7 +9,7 @@ import app.notesr.activity.security.ReEncryptionActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; /** diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index ad14fbcf..93d7ba91 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -25,7 +25,7 @@ import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.service.security.AppSecurityService; import app.notesr.service.migration.DataVersionManager; -import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java b/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java index 3b72e397..5360c4e4 100644 --- a/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java +++ b/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java @@ -20,8 +20,8 @@ import app.notesr.activity.DialogFactory; import app.notesr.activity.note.list.NotesListActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService; -import app.notesr.service.security.crypto.update.SecretsUpdateAndroidServiceStarter; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; public final class ReEncryptionActivity extends ActivityBase { diff --git a/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java b/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java index 3a5708a8..26072a13 100644 --- a/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java +++ b/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java @@ -9,7 +9,7 @@ import android.content.Context; import android.content.Intent; -import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManager.java b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java similarity index 89% rename from service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManager.java rename to service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java index 87f17ede..f68b23ff 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManager.java +++ b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import app.notesr.data.AppDatabase; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManagerImpl.java b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManagerImpl.java similarity index 96% rename from service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManagerImpl.java rename to service/src/main/java/app/notesr/service/security/rotation/DatabaseManagerImpl.java index 84117feb..2c2fd279 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/DatabaseManagerImpl.java +++ b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManagerImpl.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import android.content.Context; import android.util.Log; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java similarity index 99% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidService.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index 65fb0564..019b2045 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import static java.util.Objects.requireNonNull; import static app.notesr.core.util.CharUtils.bytesToChars; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java similarity index 95% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarter.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java index f7ef997d..ceb53956 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import static java.util.Objects.requireNonNull; import static app.notesr.core.util.CharUtils.charsToBytes; -import static app.notesr.service.security.crypto.update.SecretsUpdateAndroidService.EXTRA_CURRENT_STATE; +import static app.notesr.service.security.rotation.SecretsUpdateAndroidService.EXTRA_CURRENT_STATE; import android.content.Context; import android.content.Intent; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateFailedException.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java similarity index 87% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateFailedException.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java index 0e39974f..e3dc9eff 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateFailedException.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; public final class SecretsUpdateFailedException extends RuntimeException { diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java similarity index 99% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateService.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java index 0446030a..4e5e2c4b 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import android.content.Context; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateState.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java similarity index 95% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateState.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java index a9bb421d..23de7e8f 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateState.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import java.io.Serializable; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStateHolder.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java similarity index 93% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStateHolder.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java index 7b86ce79..d3c01eb0 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStateHolder.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import java.util.function.Consumer; diff --git a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStatus.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java similarity index 92% rename from service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStatus.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java index 1dc21809..7976fbbe 100644 --- a/service/src/main/java/app/notesr/service/security/crypto/update/SecretsUpdateStatus.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java similarity index 99% rename from service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarterTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java index 3cb9edec..e9e691eb 100644 --- a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java similarity index 99% rename from service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java index 1455174d..3a2198fe 100644 --- a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java similarity index 99% rename from service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateServiceTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java index 651d6243..8bb6f57b 100644 --- a/service/src/test/java/app/notesr/service/security/crypto/update/SecretsUpdateServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ -package app.notesr.service.security.crypto.update; +package app.notesr.service.security.rotation; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; From 4d827919969ab8d82f83882a6e690e3dd86d794b Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:40:04 +0200 Subject: [PATCH 02/30] Rename ReEncryptionActivity and associated resources to SecretsRotationActivity --- app/src/main/AndroidManifest.xml | 2 +- .../java/app/notesr/activity/FsaResolver.java | 4 ++-- .../security/KeySetupCompletionHandler.java | 6 +++--- ...ctivity.java => SecretsRotationActivity.java} | 16 ++++++++-------- ...ryption.xml => activity_secrets_rotation.xml} | 12 ++++++------ 5 files changed, 20 insertions(+), 20 deletions(-) rename app/src/main/java/app/notesr/activity/security/{ReEncryptionActivity.java => SecretsRotationActivity.java} (83%) rename app/src/main/res/layout/{activity_re_encryption.xml => activity_secrets_rotation.xml} (79%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0eb3d4d4..229a53e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,7 +51,7 @@ android:screenOrientation="locked" android:theme="@style/AppTheme" /> diff --git a/app/src/main/java/app/notesr/activity/FsaResolver.java b/app/src/main/java/app/notesr/activity/FsaResolver.java index 8beab245..48359d3e 100644 --- a/app/src/main/java/app/notesr/activity/FsaResolver.java +++ b/app/src/main/java/app/notesr/activity/FsaResolver.java @@ -7,7 +7,7 @@ import java.util.Set; -import app.notesr.activity.security.ReEncryptionActivity; +import app.notesr.activity.security.SecretsRotationActivity; import app.notesr.service.AndroidServiceRegistry; import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @@ -30,7 +30,7 @@ public final class FsaResolver { // new FsaEntry(AppMigrationAndroidService.class, MigrationActivity.class), // new FsaEntry(ExportAndroidService.class, ExportActivity.class), // new FsaEntry(ImportAndroidService.class, ImportActivity.class), - new FsaEntry(SecretsUpdateAndroidService.class, ReEncryptionActivity.class) + new FsaEntry(SecretsUpdateAndroidService.class, SecretsRotationActivity.class) ); private final AndroidServiceRegistry servicesRegistry; diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index 93d7ba91..e44d609a 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -94,10 +94,10 @@ private void onRegenerationConfirmed() { throw new RuntimeException(e); } - Intent reEncryptionIntent = new Intent(activity.getApplicationContext(), - ReEncryptionActivity.class); + Intent secretsRotationIntent = new Intent(activity.getApplicationContext(), + SecretsRotationActivity.class); - activity.startActivity(reEncryptionIntent); + activity.startActivity(secretsRotationIntent); activity.finish(); } diff --git a/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java similarity index 83% rename from app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java rename to app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index 5360c4e4..ce0e8d73 100644 --- a/app/src/main/java/app/notesr/activity/security/ReEncryptionActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -23,23 +23,23 @@ import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; -public final class ReEncryptionActivity extends ActivityBase { +public final class SecretsRotationActivity extends ActivityBase { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_re_encryption); + setContentView(R.layout.activity_secrets_rotation); applyInsets(findViewById(R.id.main)); disableBackButton(this); ReEncryptionBroadcastReceiver broadcastReceiver = - new ReEncryptionBroadcastReceiver(this::onReEncryptionComplete, - this::onReEncryptionFailed); + new ReEncryptionBroadcastReceiver(this::onSecretsRotationComplete, + this::onSecretsRotationFailed); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(SecretsUpdateAndroidService.BROADCAST_ACTION)); - startReEncryptionService(); + startSecretsRotationService(); } @Override @@ -47,7 +47,7 @@ protected boolean requiresSession() { return false; } - private void startReEncryptionService() { + private void startSecretsRotationService() { AndroidServiceRegistry serviceRegistry = AndroidServiceRegistry .getInstance(getApplicationContext()); @@ -60,12 +60,12 @@ private void startReEncryptionService() { } } - private void onReEncryptionComplete() { + private void onSecretsRotationComplete() { startActivity(new Intent(getApplicationContext(), NotesListActivity.class)); finish(); } - private void onReEncryptionFailed() { + private void onSecretsRotationFailed() { DialogFactory dialogFactory = new DialogFactory(this); dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_failed) .setTitle(R.string.error) diff --git a/app/src/main/res/layout/activity_re_encryption.xml b/app/src/main/res/layout/activity_secrets_rotation.xml similarity index 79% rename from app/src/main/res/layout/activity_re_encryption.xml rename to app/src/main/res/layout/activity_secrets_rotation.xml index 046970a2..85e90aa6 100644 --- a/app/src/main/res/layout/activity_re_encryption.xml +++ b/app/src/main/res/layout/activity_secrets_rotation.xml @@ -6,10 +6,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/activity_background" - tools:context=".activity.security.ReEncryptionActivity"> + tools:context=".activity.security.SecretsRotationActivity"> + app:layout_constraintTop_toBottomOf="@+id/secretsRotationTitleLabel" /> + app:layout_constraintTop_toBottomOf="@+id/secretsRotationProgressBar" /> \ No newline at end of file From 8746c8f0e2f743508ce10b98c5f43aac9d3fa478 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:42:14 +0200 Subject: [PATCH 03/30] Rename re-encryption failed dialog layout to secrets rotation failed --- .../app/notesr/activity/security/SecretsRotationActivity.java | 2 +- ...encryption_failed.xml => dialog_secrets_rotation_failed.xml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename app/src/main/res/layout/{dialog_re_encryption_failed.xml => dialog_secrets_rotation_failed.xml} (100%) diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index ce0e8d73..fc4b4e4c 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -67,7 +67,7 @@ private void onSecretsRotationComplete() { private void onSecretsRotationFailed() { DialogFactory dialogFactory = new DialogFactory(this); - dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_failed) + dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_secrets_rotation_failed) .setTitle(R.string.error) .setCancelable(false) .setPositiveButton(R.string.ok, (dialog, which) -> { diff --git a/app/src/main/res/layout/dialog_re_encryption_failed.xml b/app/src/main/res/layout/dialog_secrets_rotation_failed.xml similarity index 100% rename from app/src/main/res/layout/dialog_re_encryption_failed.xml rename to app/src/main/res/layout/dialog_secrets_rotation_failed.xml From f9a59c278731924fcdff41ca40e6d826ea554e70 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:43:20 +0200 Subject: [PATCH 04/30] Rename re-encryption warning dialog to secrets rotation warning --- .../app/notesr/activity/security/KeySetupCompletionHandler.java | 2 +- ...cryption_warning.xml => dialog_secrets_rotation_warning.xml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename app/src/main/res/layout/{dialog_re_encryption_warning.xml => dialog_secrets_rotation_warning.xml} (100%) diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index e44d609a..81d9f5c0 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -73,7 +73,7 @@ private void proceedFirstRun() { private void proceedRegeneration() { new DialogFactory(activity) - .getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning) + .getThemedAlertDialogBuilder(R.layout.dialog_secrets_rotation_warning) .setTitle(R.string.warning) .setPositiveButton(R.string.yes, (dialog, which) -> onRegenerationConfirmed()) diff --git a/app/src/main/res/layout/dialog_re_encryption_warning.xml b/app/src/main/res/layout/dialog_secrets_rotation_warning.xml similarity index 100% rename from app/src/main/res/layout/dialog_re_encryption_warning.xml rename to app/src/main/res/layout/dialog_secrets_rotation_warning.xml From 1c7236f3442491197826511377255e7e2f23c45a Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:45:39 +0200 Subject: [PATCH 05/30] Rename re-encryption failed error label to secrets rotation failed error label --- app/src/main/res/layout/dialog_secrets_rotation_failed.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_secrets_rotation_failed.xml b/app/src/main/res/layout/dialog_secrets_rotation_failed.xml index d041268a..102bcd7e 100644 --- a/app/src/main/res/layout/dialog_secrets_rotation_failed.xml +++ b/app/src/main/res/layout/dialog_secrets_rotation_failed.xml @@ -7,7 +7,7 @@ android:background="@color/dialog_background"> Date: Wed, 29 Jul 2026 10:47:00 +0200 Subject: [PATCH 06/30] Rename ReEncryptionBroadcastReceiver to SecretsRotationBroadcastReceiver --- .../activity/security/SecretsRotationActivity.java | 4 ++-- ...iver.java => SecretsRotationBroadcastReceiver.java} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename app/src/main/java/app/notesr/activity/security/{ReEncryptionBroadcastReceiver.java => SecretsRotationBroadcastReceiver.java} (75%) diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index fc4b4e4c..c7104fbe 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -32,8 +32,8 @@ protected void onCreate(Bundle savedInstanceState) { applyInsets(findViewById(R.id.main)); disableBackButton(this); - ReEncryptionBroadcastReceiver broadcastReceiver = - new ReEncryptionBroadcastReceiver(this::onSecretsRotationComplete, + SecretsRotationBroadcastReceiver broadcastReceiver = + new SecretsRotationBroadcastReceiver(this::onSecretsRotationComplete, this::onSecretsRotationFailed); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, diff --git a/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java similarity index 75% rename from app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java rename to app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java index 26072a13..a95f9578 100644 --- a/app/src/main/java/app/notesr/activity/security/ReEncryptionBroadcastReceiver.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java @@ -13,9 +13,9 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public final class ReEncryptionBroadcastReceiver extends BroadcastReceiver { - private final Runnable onReEncryptionComplete; - private final Runnable onReEncryptionFailed; +public final class SecretsRotationBroadcastReceiver extends BroadcastReceiver { + private final Runnable onSecretsRotationComplete; + private final Runnable onSecretsRotationFailed; @Override public void onReceive(Context context, Intent intent) { @@ -27,9 +27,9 @@ public void onReceive(Context context, Intent intent) { false); if (isCompleted) { - onReEncryptionComplete.run(); + onSecretsRotationComplete.run(); } else if (isFailed) { - onReEncryptionFailed.run(); + onSecretsRotationFailed.run(); } } } From 25ab8b4eb77733fd6ad1b31cec01f5f9db542d2a Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 10:51:08 +0200 Subject: [PATCH 07/30] Rename regenerate or import key to rotate key in menu and update associated references --- .../java/app/notesr/activity/note/list/NotesListActivity.java | 2 +- app/src/main/res/menu/menu_notes_list.xml | 4 ++-- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java b/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java index adce0906..eb2b97b7 100644 --- a/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java +++ b/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java @@ -105,7 +105,7 @@ public boolean onCreateOptionsMenu(Menu menu) { menuActions.put(R.id.lockAppButton, lockAction::lock); menuActions.put(R.id.changePasswordMenuItem, this::startChangePasswordActivity); - menuActions.put(R.id.generateNewKeyMenuItem, generateNewKeyAction::startActivity); + menuActions.put(R.id.rotateKey, generateNewKeyAction::startActivity); menuActions.put(R.id.exportMenuItem, () -> startActivity(new Intent(this, ExportActivity.class))); menuActions.put(R.id.importMenuItem, diff --git a/app/src/main/res/menu/menu_notes_list.xml b/app/src/main/res/menu/menu_notes_list.xml index 4d81f237..a583a923 100644 --- a/app/src/main/res/menu/menu_notes_list.xml +++ b/app/src/main/res/menu/menu_notes_list.xml @@ -23,6 +23,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6baaf31..628f07a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,7 +48,7 @@ Change access code Create new access code Updated! - Regenerate or import key + Rotate key This action will cause all of your data to be re-encrypted.\nAre you sure? Re-encrypting data… Warning From b31cbb21355f2c7f8b973fb37caa8403b09f90bb Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:51:51 +0200 Subject: [PATCH 08/30] Rename SecretsUpdateService to SecretsRotationService --- ...ecretsUpdateService.java => SecretsRotationService.java} | 2 +- .../security/rotation/SecretsUpdateAndroidService.java | 6 +++--- ...dateServiceTest.java => SecretsRotationServiceTest.java} | 6 +++--- .../security/rotation/SecretsUpdateAndroidServiceTest.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateService.java => SecretsRotationService.java} (99%) rename service/src/test/java/app/notesr/service/security/rotation/{SecretsUpdateServiceTest.java => SecretsRotationServiceTest.java} (99%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java similarity index 99% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 4e5e2c4b..6735eabc 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -32,7 +32,7 @@ * to the new encryption settings in a transactional manner. */ @RequiredArgsConstructor -public final class SecretsUpdateService { +public final class SecretsRotationService { private final Context context; private final DatabaseManager databaseManager; diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index 019b2045..bdd57e95 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -59,7 +59,7 @@ public class SecretsUpdateAndroidService extends AndroidService implements Runna private CryptoManager cryptoManager; private SecretsUpdateStateHolder stateHolder; private CryptoSecrets newSecrets; - private SecretsUpdateService secretsUpdateService; + private SecretsRotationService secretsUpdateService; private String encryptedPayload; @Override @@ -209,10 +209,10 @@ CryptoSecrets getNewSecrets() { } } - SecretsUpdateService getSecretsUpdateService() { + SecretsRotationService getSecretsUpdateService() { var context = getApplicationContext(); var databaseManager = new DatabaseManagerImpl(context); - return new SecretsUpdateService(context, databaseManager); + return new SecretsRotationService(context, databaseManager); } } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java similarity index 99% rename from service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 8bb6f57b..41de3e2f 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -47,7 +47,7 @@ import app.notesr.data.model.Note; @ExtendWith(MockitoExtension.class) -class SecretsUpdateServiceTest { +class SecretsRotationServiceTest { private static final int KEY_SIZE = 48; @@ -62,7 +62,7 @@ class SecretsUpdateServiceTest { @Mock private Consumer onUpdate; - private SecretsUpdateService secretsUpdateService; + private SecretsRotationService secretsUpdateService; private SecretsUpdateStateHolder stateHolder; private final String dbName = "test.db"; @@ -72,7 +72,7 @@ class SecretsUpdateServiceTest { @BeforeEach void setUp() { - secretsUpdateService = new SecretsUpdateService(context, databaseManager); + secretsUpdateService = new SecretsRotationService(context, databaseManager); stateHolder = new SecretsUpdateStateHolder(onUpdate); // Initialize keys diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java index 3a2198fe..24fe0e37 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java @@ -55,7 +55,7 @@ class SecretsUpdateAndroidServiceTest { private CryptoManager cryptoManager; @Mock - private SecretsUpdateService secretsUpdateService; + private SecretsRotationService secretsUpdateService; @Mock private TransactionalFilesUtil txFiles; From 2d364f80c940d4092ed24e3b7861fa32a82549a8 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:53:19 +0200 Subject: [PATCH 09/30] Rename SecretsUpdateAndroidService to SecretsRotationAndroidService --- app/src/main/AndroidManifest.xml | 2 +- .../java/app/notesr/activity/FsaResolver.java | 4 ++-- .../security/KeySetupCompletionHandler.java | 6 +++--- .../security/SecretsRotationActivity.java | 6 +++--- .../SecretsRotationBroadcastReceiver.java | 8 ++++---- ...e.java => SecretsRotationAndroidService.java} | 4 ++-- .../SecretsUpdateAndroidServiceStarter.java | 16 ++++++++-------- ...va => SecretsRotationAndroidServiceTest.java} | 14 +++++++------- .../SecretsUpdateAndroidServiceStarterTest.java | 16 ++++++++-------- 9 files changed, 38 insertions(+), 38 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateAndroidService.java => SecretsRotationAndroidService.java} (97%) rename service/src/test/java/app/notesr/service/security/rotation/{SecretsUpdateAndroidServiceTest.java => SecretsRotationAndroidServiceTest.java} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 229a53e8..fc407ee2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -117,7 +117,7 @@ android:stopWithTask="false" android:foregroundServiceType="dataSync" /> diff --git a/app/src/main/java/app/notesr/activity/FsaResolver.java b/app/src/main/java/app/notesr/activity/FsaResolver.java index 48359d3e..5d9e809b 100644 --- a/app/src/main/java/app/notesr/activity/FsaResolver.java +++ b/app/src/main/java/app/notesr/activity/FsaResolver.java @@ -9,7 +9,7 @@ import app.notesr.activity.security.SecretsRotationActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.rotation.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsRotationAndroidService; import lombok.RequiredArgsConstructor; /** @@ -30,7 +30,7 @@ public final class FsaResolver { // new FsaEntry(AppMigrationAndroidService.class, MigrationActivity.class), // new FsaEntry(ExportAndroidService.class, ExportActivity.class), // new FsaEntry(ImportAndroidService.class, ImportActivity.class), - new FsaEntry(SecretsUpdateAndroidService.class, SecretsRotationActivity.class) + new FsaEntry(SecretsRotationAndroidService.class, SecretsRotationActivity.class) ); private final AndroidServiceRegistry servicesRegistry; diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index 81d9f5c0..97110cdf 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -25,7 +25,7 @@ import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.service.security.AppSecurityService; import app.notesr.service.migration.DataVersionManager; -import app.notesr.service.security.rotation.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsRotationAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -88,8 +88,8 @@ private void onRegenerationConfirmed() { char[] password = getCurrentPassword(); byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); - SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, keyBytes); - SecretCache.put(SecretsUpdateAndroidService.PASSWORD, passwordBytes); + SecretCache.put(SecretsRotationAndroidService.NEW_KEY, keyBytes); + SecretCache.put(SecretsRotationAndroidService.PASSWORD, passwordBytes); } catch (CharacterCodingException e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index c7104fbe..10c5ac7e 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -20,7 +20,7 @@ import app.notesr.activity.DialogFactory; import app.notesr.activity.note.list.NotesListActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.rotation.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsRotationAndroidService; import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; public final class SecretsRotationActivity extends ActivityBase { @@ -37,7 +37,7 @@ protected void onCreate(Bundle savedInstanceState) { this::onSecretsRotationFailed); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, - new IntentFilter(SecretsUpdateAndroidService.BROADCAST_ACTION)); + new IntentFilter(SecretsRotationAndroidService.BROADCAST_ACTION)); startSecretsRotationService(); } @@ -51,7 +51,7 @@ private void startSecretsRotationService() { AndroidServiceRegistry serviceRegistry = AndroidServiceRegistry .getInstance(getApplicationContext()); - if (!serviceRegistry.isServiceRunning(SecretsUpdateAndroidService.class)) { + if (!serviceRegistry.isServiceRunning(SecretsRotationAndroidService.class)) { try { new SecretsUpdateAndroidServiceStarter().start(getApplicationContext()); } catch (CharacterCodingException e) { diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java index a95f9578..2fb449f5 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java @@ -9,7 +9,7 @@ import android.content.Context; import android.content.Intent; -import app.notesr.service.security.rotation.SecretsUpdateAndroidService; +import app.notesr.service.security.rotation.SecretsRotationAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -19,11 +19,11 @@ public final class SecretsRotationBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (SecretsUpdateAndroidService.BROADCAST_ACTION.equals(intent.getAction())) { - boolean isCompleted = intent.getBooleanExtra(SecretsUpdateAndroidService.EXTRA_COMPLETE, + if (SecretsRotationAndroidService.BROADCAST_ACTION.equals(intent.getAction())) { + boolean isCompleted = intent.getBooleanExtra(SecretsRotationAndroidService.EXTRA_COMPLETE, false); - boolean isFailed = intent.getBooleanExtra(SecretsUpdateAndroidService.EXTRA_FAIL, + boolean isFailed = intent.getBooleanExtra(SecretsRotationAndroidService.EXTRA_FAIL, false); if (isCompleted) { diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java similarity index 97% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index bdd57e95..edd7fe8c 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -42,9 +42,9 @@ import lombok.Setter; @Setter(AccessLevel.PACKAGE) -public class SecretsUpdateAndroidService extends AndroidService implements Runnable { +public class SecretsRotationAndroidService extends AndroidService implements Runnable { - private static final String TAG = SecretsUpdateAndroidService.class.getSimpleName(); + private static final String TAG = SecretsRotationAndroidService.class.getSimpleName(); public static final String NEW_KEY = "new_key"; public static final String PASSWORD = "password"; diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java index ceb53956..c1d9198f 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java @@ -7,7 +7,7 @@ import static java.util.Objects.requireNonNull; import static app.notesr.core.util.CharUtils.charsToBytes; -import static app.notesr.service.security.rotation.SecretsUpdateAndroidService.EXTRA_CURRENT_STATE; +import static app.notesr.service.security.rotation.SecretsRotationAndroidService.EXTRA_CURRENT_STATE; import android.content.Context; import android.content.Intent; @@ -38,16 +38,16 @@ public final class SecretsUpdateAndroidServiceStarter implements AndroidServiceS @Override public void start(Context context) throws CharacterCodingException { if (payload == null) { - if (!SecretCache.contains(SecretsUpdateAndroidService.NEW_KEY) - || !SecretCache.contains(SecretsUpdateAndroidService.PASSWORD)) { + if (!SecretCache.contains(SecretsRotationAndroidService.NEW_KEY) + || !SecretCache.contains(SecretsRotationAndroidService.PASSWORD)) { throw new IllegalStateException("Secrets are not set"); } } else { - SecretCache.removeIfExists(SecretsUpdateAndroidService.NEW_KEY); - SecretCache.removeIfExists(SecretsUpdateAndroidService.PASSWORD); + SecretCache.removeIfExists(SecretsRotationAndroidService.NEW_KEY); + SecretCache.removeIfExists(SecretsRotationAndroidService.PASSWORD); - SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, payload.getNewKey()); - SecretCache.put(SecretsUpdateAndroidService.PASSWORD, + SecretCache.put(SecretsRotationAndroidService.NEW_KEY, payload.getNewKey()); + SecretCache.put(SecretsRotationAndroidService.PASSWORD, charsToBytes(payload.getNewPassword(), StandardCharsets.UTF_8)); } @@ -81,7 +81,7 @@ private Payload decryptPayload(ObjectMapper mapper, CryptoSecrets secrets, Strin } private Intent buildIntent(Context context) { - return new Intent(context, SecretsUpdateAndroidService.class) + return new Intent(context, SecretsRotationAndroidService.class) .putExtra(EXTRA_CURRENT_STATE, state); } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java similarity index 93% rename from service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index 24fe0e37..d99a9acf 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -43,7 +43,7 @@ import app.notesr.service.AndroidServiceRegistry; @ExtendWith(MockitoExtension.class) -class SecretsUpdateAndroidServiceTest { +class SecretsRotationAndroidServiceTest { @Mock private Context context; @@ -66,13 +66,13 @@ class SecretsUpdateAndroidServiceTest { @Mock private AndroidServiceRegistry androidServiceRegistry; - private SecretsUpdateAndroidService service; + private SecretsRotationAndroidService service; private SecretsUpdateState state; private CryptoSecrets newSecrets; @BeforeEach void setUp() { - service = spy(new SecretsUpdateAndroidService()); + service = spy(new SecretsRotationAndroidService()); state = new SecretsUpdateState(); newSecrets = new CryptoSecrets(new byte[32], "password".toCharArray()); @@ -130,14 +130,14 @@ void testRunFailure() { void testOnCompleteCallsSendBroadcast() { doNothing().when(service).sendUpdateBroadcast(anyString()); service.onComplete(); - verify(service).sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_COMPLETE); + verify(service).sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_COMPLETE); } @Test void testOnFailCallsSendBroadcast() { doNothing().when(service).sendUpdateBroadcast(anyString()); service.onFail(); - verify(service).sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_FAIL); + verify(service).sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_FAIL); } @Test @@ -153,7 +153,7 @@ void testSendUpdateBroadcast() { doReturn(mock).when(mock).putExtra(anyString(), anyBoolean())); try (mockedIntent) { - service.sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_COMPLETE); + service.sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_COMPLETE); verify(localBroadcastManager).sendBroadcast(any(Intent.class)); } @@ -188,7 +188,7 @@ void testOnStateUpdateUpdatesRegistry() { @Test void testOnStartCommand() { - when(intent.getSerializableExtra(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE)) + when(intent.getSerializableExtra(SecretsRotationAndroidService.EXTRA_CURRENT_STATE)) .thenReturn(state); try (MockedStatic registryMock = mockStatic(AndroidServiceRegistry.class)) { diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java index e9e691eb..3003aa1e 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java @@ -71,8 +71,8 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { byte[] newKey = "newKey".getBytes(); byte[] password = "password".getBytes(); - SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, newKey); - SecretCache.put(SecretsUpdateAndroidService.PASSWORD, password); + SecretCache.put(SecretsRotationAndroidService.NEW_KEY, newKey); + SecretCache.put(SecretsRotationAndroidService.PASSWORD, password); MockedConstruction mockedIntent = mockConstruction(Intent.class, (mock, context) -> @@ -85,7 +85,7 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { verify(context).startForegroundService(any(Intent.class)); Intent constructed = mockedIntent.constructed().get(0); verify(constructed) - .putExtra(eq(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE), + .putExtra(eq(SecretsRotationAndroidService.EXTRA_CURRENT_STATE), (Serializable) eq(null)); } } @@ -110,9 +110,9 @@ void testStartWithPayloadUpdatesCacheAndStartsService() throws Exception { starter.start(context); assertArrayEquals(expectedNewKey, - SecretCache.take(SecretsUpdateAndroidService.NEW_KEY)); + SecretCache.take(SecretsRotationAndroidService.NEW_KEY)); assertArrayEquals(expectedPasswordBytes, - SecretCache.take(SecretsUpdateAndroidService.PASSWORD)); + SecretCache.take(SecretsRotationAndroidService.PASSWORD)); verify(context).startForegroundService(any(Intent.class)); } @@ -153,13 +153,13 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { starter.start(context, secrets, encryptedPayload, stateJson); assertArrayEquals(expectedNewKey, - SecretCache.take(SecretsUpdateAndroidService.NEW_KEY)); + SecretCache.take(SecretsRotationAndroidService.NEW_KEY)); assertArrayEquals(expectedNewPasswordBytes, - SecretCache.take(SecretsUpdateAndroidService.PASSWORD)); + SecretCache.take(SecretsRotationAndroidService.PASSWORD)); verify(context).startForegroundService(any(Intent.class)); Intent constructed = mockedIntent.constructed().get(0); - verify(constructed).putExtra(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE, state); + verify(constructed).putExtra(SecretsRotationAndroidService.EXTRA_CURRENT_STATE, state); } } From 704176e95f35f4a7209ab3e61d211ac4d55d097d Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:54:35 +0200 Subject: [PATCH 10/30] Rename SecretsUpdateAndroidServiceStarter to SecretsRotationAndroidServiceStarter --- .../security/SecretsRotationActivity.java | 4 ++-- .../SecretsRotationAndroidService.java | 8 +++---- ...SecretsRotationAndroidServiceStarter.java} | 2 +- ...etsRotationAndroidServiceStarterTest.java} | 24 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateAndroidServiceStarter.java => SecretsRotationAndroidServiceStarter.java} (97%) rename service/src/test/java/app/notesr/service/security/rotation/{SecretsUpdateAndroidServiceStarterTest.java => SecretsRotationAndroidServiceStarterTest.java} (89%) diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index 10c5ac7e..9c451c01 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -21,7 +21,7 @@ import app.notesr.activity.note.list.NotesListActivity; import app.notesr.service.AndroidServiceRegistry; import app.notesr.service.security.rotation.SecretsRotationAndroidService; -import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; +import app.notesr.service.security.rotation.SecretsRotationAndroidServiceStarter; public final class SecretsRotationActivity extends ActivityBase { @@ -53,7 +53,7 @@ private void startSecretsRotationService() { if (!serviceRegistry.isServiceRunning(SecretsRotationAndroidService.class)) { try { - new SecretsUpdateAndroidServiceStarter().start(getApplicationContext()); + new SecretsRotationAndroidServiceStarter().start(getApplicationContext()); } catch (CharacterCodingException e) { throw new RuntimeException(e); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index edd7fe8c..7ae9c2fd 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -105,7 +105,7 @@ void showForegroundNotification(int startId) { @NonNull @Override protected AndroidServiceEntry getEntry(String payload, String state) { - return entryBuilder(SecretsUpdateAndroidServiceStarter.class) + return entryBuilder(SecretsRotationAndroidServiceStarter.class) .autoStart(true) .requiresAuth(true) .payload(payload) @@ -113,14 +113,14 @@ protected AndroidServiceEntry getEntry(String payload, String state) { .build(); } - private SecretsUpdateAndroidServiceStarter.Payload getPayload() { - return new SecretsUpdateAndroidServiceStarter.Payload( + private SecretsRotationAndroidServiceStarter.Payload getPayload() { + return new SecretsRotationAndroidServiceStarter.Payload( newSecrets.getKey(), newSecrets.getPassword() ); } - String encryptPayload(SecretsUpdateAndroidServiceStarter.Payload payload) { + String encryptPayload(SecretsRotationAndroidServiceStarter.Payload payload) { return getEncryptedJson(new ObjectMapper(), payload, cryptoManager.getSecrets()); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java similarity index 97% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java index c1d9198f..f7893a05 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java @@ -30,7 +30,7 @@ @NoArgsConstructor @AllArgsConstructor -public final class SecretsUpdateAndroidServiceStarter implements AndroidServiceStarter { +public final class SecretsRotationAndroidServiceStarter implements AndroidServiceStarter { private Payload payload; private SecretsUpdateState state; diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java similarity index 89% rename from service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java index 3003aa1e..3964574d 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java @@ -39,14 +39,14 @@ import app.notesr.core.util.KeyUtils; @ExtendWith(MockitoExtension.class) -class SecretsUpdateAndroidServiceStarterTest { +class SecretsRotationAndroidServiceStarterTest { private static final int KEY_SIZE = 48; @Mock private Context context; - private SecretsUpdateAndroidServiceStarter starter; + private SecretsRotationAndroidServiceStarter starter; private ObjectMapper mapper; @BeforeEach @@ -62,7 +62,7 @@ void tearDown() { @Test void testStartNullPayloadNoCacheThrowsIllegalStateException() { - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); assertThrows(IllegalStateException.class, () -> starter.start(context)); } @@ -79,7 +79,7 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); starter.start(context); verify(context).startForegroundService(any(Intent.class)); @@ -99,14 +99,14 @@ void testStartWithPayloadUpdatesCacheAndStartsService() throws Exception { char[] newPassword = passwordStr.toCharArray(); byte[] expectedPasswordBytes = passwordStr.getBytes(StandardCharsets.UTF_8); - var payload = new SecretsUpdateAndroidServiceStarter.Payload(newKey, newPassword); + var payload = new SecretsRotationAndroidServiceStarter.Payload(newKey, newPassword); MockedConstruction mockedIntent = mockConstruction(Intent.class, (mock, context) -> when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsUpdateAndroidServiceStarter(payload, null); + starter = new SecretsRotationAndroidServiceStarter(payload, null); starter.start(context); assertArrayEquals(expectedNewKey, @@ -132,7 +132,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { char[] newPassword = newPasswordStr.toCharArray(); byte[] expectedNewPasswordBytes = newPasswordStr.getBytes(StandardCharsets.UTF_8); - var payload = new SecretsUpdateAndroidServiceStarter.Payload(newKey, newPassword); + var payload = new SecretsRotationAndroidServiceStarter.Payload(newKey, newPassword); String payloadJson = mapper.writeValueAsString(payload); @@ -149,7 +149,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); starter.start(context, secrets, encryptedPayload, stateJson); assertArrayEquals(expectedNewKey, @@ -170,7 +170,7 @@ void testStartWithEncryptedDataInvalidSecretsThrowsDecryptionFailedException() { String encryptedPayload = "invalid_payload"; - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); assertThrows(DecryptionFailedException.class, () -> starter.start(context, secrets, encryptedPayload, null)); } @@ -185,14 +185,14 @@ void testStartWithEncryptedDataInvalidJsonThrowsJsonProcessingException() throws KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt("not a json"); - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); assertThrows(JsonProcessingException.class, () -> starter.start(context, secrets, encryptedPayload, null)); } @Test void testStartWithEncryptedDataNullSecretsThrowsNullPointerException() { - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); assertThrows(NullPointerException.class, () -> starter.start(context, null, "payload", null)); } @@ -201,7 +201,7 @@ void testStartWithEncryptedDataNullSecretsThrowsNullPointerException() { void testStartWithEncryptedDataNullPayloadThrowsNullPointerException() { byte[] key = new byte[KEY_SIZE]; CryptoSecrets secrets = new CryptoSecrets(key, "pass".toCharArray()); - starter = new SecretsUpdateAndroidServiceStarter(); + starter = new SecretsRotationAndroidServiceStarter(); assertThrows(NullPointerException.class, () -> starter.start(context, secrets, null, null)); } From 2e9e91745cd8524de62aa4006837ac10d6f38e27 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:55:42 +0200 Subject: [PATCH 11/30] Rename SecretsUpdateState to SecretsRotationState --- .../rotation/SecretsRotationAndroidService.java | 8 ++++---- .../SecretsRotationAndroidServiceStarter.java | 4 ++-- ...etsUpdateState.java => SecretsRotationState.java} | 10 +++++----- .../security/rotation/SecretsUpdateStateHolder.java | 12 ++++++------ .../SecretsRotationAndroidServiceStarterTest.java | 2 +- .../rotation/SecretsRotationAndroidServiceTest.java | 4 ++-- .../rotation/SecretsRotationServiceTest.java | 12 ++++++------ 7 files changed, 26 insertions(+), 26 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateState.java => SecretsRotationState.java} (64%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 7ae9c2fd..4d798879 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -69,7 +69,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { cryptoManager = CryptoManagerProvider.getInstance(getApplicationContext()); newSecrets = getNewSecrets(); - var state = (SecretsUpdateState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); + var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); stateHolder = new SecretsUpdateStateHolder(this::onStateUpdate).setState(state); secretsUpdateService = getSecretsUpdateService(); encryptedPayload = encryptPayload(getPayload()); @@ -124,7 +124,7 @@ String encryptPayload(SecretsRotationAndroidServiceStarter.Payload payload) { return getEncryptedJson(new ObjectMapper(), payload, cryptoManager.getSecrets()); } - String serializeState(SecretsUpdateState state) { + String serializeState(SecretsRotationState state) { if (state == null) { return null; } @@ -162,7 +162,7 @@ void stopService() { stopSelf(); } - TransactionalFilesUtil getTransactionalFilesUtil(SecretsUpdateState state) { + TransactionalFilesUtil getTransactionalFilesUtil(SecretsRotationState state) { var filesUtils = new FilesUtils(); var transactionId = state.getTransactionId(); @@ -176,7 +176,7 @@ public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); } - void onStateUpdate(SecretsUpdateState newState) { + void onStateUpdate(SecretsRotationState newState) { AndroidServiceRegistry.getInstance(getApplicationContext()) .updateEntry(getEntry(encryptedPayload, serializeState(newState))); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java index f7893a05..7b0762b5 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java @@ -33,7 +33,7 @@ public final class SecretsRotationAndroidServiceStarter implements AndroidServiceStarter { private Payload payload; - private SecretsUpdateState state; + private SecretsRotationState state; @Override public void start(Context context) throws CharacterCodingException { @@ -61,7 +61,7 @@ public void start(Context context, CryptoSecrets secrets, String payload, String var mapper = new ObjectMapper(); if (state != null) { - this.state = mapper.readValue(state, SecretsUpdateState.class); + this.state = mapper.readValue(state, SecretsRotationState.class); } this.payload = decryptPayload(mapper, secrets, payload); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java similarity index 64% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java index 23de7e8f..8880430d 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java @@ -17,7 +17,7 @@ @NoArgsConstructor @ToString @EqualsAndHashCode -public final class SecretsUpdateState implements Serializable { +public final class SecretsRotationState implements Serializable { @Getter private SecretsUpdateStatus status; @@ -25,21 +25,21 @@ public final class SecretsUpdateState implements Serializable { @Getter private String transactionId; - public SecretsUpdateState setStatus(SecretsUpdateStatus status) { + public SecretsRotationState setStatus(SecretsUpdateStatus status) { this.status = status; return this; } - public SecretsUpdateState setTransactionId(String transactionId) { + public SecretsRotationState setTransactionId(String transactionId) { this.transactionId = transactionId; return this; } - public static SecretsUpdateState from(SecretsUpdateState state) { + public static SecretsRotationState from(SecretsRotationState state) { if (state == null) { return null; } - return new SecretsUpdateState(state.getStatus(), state.getTransactionId()); + return new SecretsRotationState(state.getStatus(), state.getTransactionId()); } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java index d3c01eb0..aed8e27d 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java @@ -12,20 +12,20 @@ @RequiredArgsConstructor public final class SecretsUpdateStateHolder { - private final Consumer onUpdate; + private final Consumer onUpdate; - private SecretsUpdateState state = new SecretsUpdateState(); + private SecretsRotationState state = new SecretsRotationState(); - public SecretsUpdateState getState() { - return SecretsUpdateState.from(state); + public SecretsRotationState getState() { + return SecretsRotationState.from(state); } - public SecretsUpdateStateHolder setState(SecretsUpdateState newState) { + public SecretsUpdateStateHolder setState(SecretsRotationState newState) { if (newState == null) { return this; } - state = SecretsUpdateState.from(newState); + state = SecretsRotationState.from(newState); onUpdate.accept(state); return this; diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java index 3964574d..ca11c175 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java @@ -140,7 +140,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt(payloadJson); - SecretsUpdateState state = new SecretsUpdateState(SecretsUpdateStatus.MOVING_DB_DATA, + SecretsRotationState state = new SecretsRotationState(SecretsUpdateStatus.MOVING_DB_DATA, "tx123"); String stateJson = mapper.writeValueAsString(state); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index d99a9acf..911ca1b8 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -67,13 +67,13 @@ class SecretsRotationAndroidServiceTest { private AndroidServiceRegistry androidServiceRegistry; private SecretsRotationAndroidService service; - private SecretsUpdateState state; + private SecretsRotationState state; private CryptoSecrets newSecrets; @BeforeEach void setUp() { service = spy(new SecretsRotationAndroidService()); - state = new SecretsUpdateState(); + state = new SecretsRotationState(); newSecrets = new CryptoSecrets(new byte[32], "password".toCharArray()); // Inject basic dependencies using setters diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 41de3e2f..9421f4b5 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -60,7 +60,7 @@ class SecretsRotationServiceTest { @Mock private CryptoManager cryptoManager; @Mock - private Consumer onUpdate; + private Consumer onUpdate; private SecretsRotationService secretsUpdateService; private SecretsUpdateStateHolder stateHolder; @@ -93,7 +93,7 @@ private CryptoSecrets createNewSecrets() { @Test void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsUpdateState().setStatus(SecretsUpdateStatus.DONE)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); @@ -107,7 +107,7 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { @Test void testUpdateSecretsAlreadyFailedThrowsException() { when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsUpdateState().setStatus(SecretsUpdateStatus.FAILED)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( @@ -189,7 +189,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); - stateHolder.setState(new SecretsUpdateState().setStatus(SecretsUpdateStatus.MOVING_DB_DATA)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.MOVING_DB_DATA)); when(cryptoManager.getSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); @@ -361,12 +361,12 @@ void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { assertEquals(SecretsUpdateStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), "Status should be updated in the state holder"); - verify(onUpdate).accept(any(SecretsUpdateState.class)); + verify(onUpdate).accept(any(SecretsRotationState.class)); } @Test void testGetStatusReturnsStatusFromStateHolder() { - stateHolder.setState(new SecretsUpdateState() + stateHolder.setState(new SecretsRotationState() .setStatus(SecretsUpdateStatus.MOVING_BLOBS_DATA)); SecretsUpdateStatus status = secretsUpdateService.getStatus(stateHolder); From 70c6bf47e795e07b4bca6307a4de60e7d259bd72 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:56:30 +0200 Subject: [PATCH 12/30] Rename SecretsUpdateStateHolder to SecretsRotationStateHolder --- .../security/rotation/SecretsRotationAndroidService.java | 4 ++-- .../service/security/rotation/SecretsRotationService.java | 8 ++++---- ...teStateHolder.java => SecretsRotationStateHolder.java} | 4 ++-- .../rotation/SecretsRotationAndroidServiceTest.java | 4 ++-- .../security/rotation/SecretsRotationServiceTest.java | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateStateHolder.java => SecretsRotationStateHolder.java} (83%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 4d798879..0227b845 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -57,7 +57,7 @@ public class SecretsRotationAndroidService extends AndroidService implements Run private String dbName; private CryptoManager cryptoManager; - private SecretsUpdateStateHolder stateHolder; + private SecretsRotationStateHolder stateHolder; private CryptoSecrets newSecrets; private SecretsRotationService secretsUpdateService; private String encryptedPayload; @@ -70,7 +70,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { newSecrets = getNewSecrets(); var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); - stateHolder = new SecretsUpdateStateHolder(this::onStateUpdate).setState(state); + stateHolder = new SecretsRotationStateHolder(this::onStateUpdate).setState(state); secretsUpdateService = getSecretsUpdateService(); encryptedPayload = encryptPayload(getPayload()); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 6735eabc..a2e43627 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -53,7 +53,7 @@ public void updateSecrets( TransactionalFilesUtil txFiles, CryptoManager cryptoManager, String dbName, - SecretsUpdateStateHolder stateHolder, + SecretsRotationStateHolder stateHolder, CryptoSecrets newSecrets) { try { @@ -133,7 +133,7 @@ public void updateSecrets( */ void migrateData( TransactionalFilesUtil txFiles, - SecretsUpdateStateHolder stateHolder, + SecretsRotationStateHolder stateHolder, String dbName, byte[] currentKey, byte[] newKey, @@ -291,7 +291,7 @@ byte[] decryptBlobData(AesCryptor cryptor, byte[] data) * @param stateHolder The state holder. * @return The current {@link SecretsUpdateStatus}. */ - SecretsUpdateStatus getStatus(SecretsUpdateStateHolder stateHolder) { + SecretsUpdateStatus getStatus(SecretsRotationStateHolder stateHolder) { return stateHolder.getState().getStatus(); } @@ -301,7 +301,7 @@ SecretsUpdateStatus getStatus(SecretsUpdateStateHolder stateHolder) { * @param stateHolder The state holder. * @param status The new status to set. */ - void setStatus(SecretsUpdateStateHolder stateHolder, SecretsUpdateStatus status) { + void setStatus(SecretsRotationStateHolder stateHolder, SecretsUpdateStatus status) { stateHolder.setState(stateHolder.getState().setStatus(status)); } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java similarity index 83% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java index aed8e27d..0abb54f8 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java @@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public final class SecretsUpdateStateHolder { +public final class SecretsRotationStateHolder { private final Consumer onUpdate; @@ -20,7 +20,7 @@ public SecretsRotationState getState() { return SecretsRotationState.from(state); } - public SecretsUpdateStateHolder setState(SecretsRotationState newState) { + public SecretsRotationStateHolder setState(SecretsRotationState newState) { if (newState == null) { return this; } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index 911ca1b8..6c100dc2 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -85,7 +85,7 @@ void setUp() { @Test void testRunSuccess() { - service.setStateHolder(new SecretsUpdateStateHolder(s -> {}) + service.setStateHolder(new SecretsRotationStateHolder(s -> {}) .setState(state)); doReturn(txFiles).when(service).getTransactionalFilesUtil(any()); @@ -105,7 +105,7 @@ void testRunSuccess() { @Test void testRunFailure() { - service.setStateHolder(new SecretsUpdateStateHolder(s -> {}) + service.setStateHolder(new SecretsRotationStateHolder(s -> {}) .setState(state)); MockedStatic logMock = mockStatic(Log.class); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 9421f4b5..5e2c985f 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -63,7 +63,7 @@ class SecretsRotationServiceTest { private Consumer onUpdate; private SecretsRotationService secretsUpdateService; - private SecretsUpdateStateHolder stateHolder; + private SecretsRotationStateHolder stateHolder; private final String dbName = "test.db"; private final byte[] currentKey = new byte[KEY_SIZE]; @@ -73,7 +73,7 @@ class SecretsRotationServiceTest { @BeforeEach void setUp() { secretsUpdateService = new SecretsRotationService(context, databaseManager); - stateHolder = new SecretsUpdateStateHolder(onUpdate); + stateHolder = new SecretsRotationStateHolder(onUpdate); // Initialize keys for (int i = 0; i < KEY_SIZE; i++) { From b62c75be4266beb2762b8f598297f1f6b66e8166 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:57:06 +0200 Subject: [PATCH 13/30] Rename SecretsUpdateStatus to SecretsRotationStatus --- .../rotation/SecretsRotationService.java | 28 +++++++++---------- .../rotation/SecretsRotationState.java | 4 +-- ...Status.java => SecretsRotationStatus.java} | 6 ++-- ...retsRotationAndroidServiceStarterTest.java | 2 +- .../rotation/SecretsRotationServiceTest.java | 24 ++++++++-------- 5 files changed, 32 insertions(+), 32 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsUpdateStatus.java => SecretsRotationStatus.java} (77%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index a2e43627..50492587 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -66,14 +66,14 @@ public void updateSecrets( try (txFiles) { if (getStatus(stateHolder) == null) { - setStatus(stateHolder, SecretsUpdateStatus.INITIALIZING); + setStatus(stateHolder, SecretsRotationStatus.INITIALIZING); } - if (getStatus(stateHolder) == SecretsUpdateStatus.DONE) { + if (getStatus(stateHolder) == SecretsRotationStatus.DONE) { return; } - if (getStatus(stateHolder) == SecretsUpdateStatus.FAILED) { + if (getStatus(stateHolder) == SecretsRotationStatus.FAILED) { throw new SecretsUpdateFailedException("Secrets update is already failed"); } @@ -97,18 +97,18 @@ public void updateSecrets( txFiles.commit(); } else { - if (getStatus(stateHolder).isBefore(SecretsUpdateStatus.DONE)) { - setStatus(stateHolder, SecretsUpdateStatus.DONE); + if (getStatus(stateHolder).isBefore(SecretsRotationStatus.DONE)) { + setStatus(stateHolder, SecretsRotationStatus.DONE); } } cryptoManager.setSecrets(context, newSecrets); - setStatus(stateHolder, SecretsUpdateStatus.DONE); + setStatus(stateHolder, SecretsRotationStatus.DONE); databaseManager.reinitProvider(newSecrets.getKey()); } catch (Exception e) { txFiles.rollback(); - setStatus(stateHolder, SecretsUpdateStatus.FAILED); + setStatus(stateHolder, SecretsRotationStatus.FAILED); throw new SecretsUpdateFailedException("Secrets update failed", e); } finally { currentSecrets.destroy(); @@ -151,12 +151,12 @@ void migrateData( var currentDbFile = txFiles.getDatabaseFile(context, dbName); try { - if (getStatus(stateHolder).isBeforeOrEqual(SecretsUpdateStatus.MOVING_BLOBS_DATA)) { - setStatus(stateHolder, SecretsUpdateStatus.MOVING_BLOBS_DATA); + if (getStatus(stateHolder).isBeforeOrEqual(SecretsRotationStatus.MOVING_BLOBS_DATA)) { + setStatus(stateHolder, SecretsRotationStatus.MOVING_BLOBS_DATA); updateBlobsData(txFiles, currentDb, currentBlobsDir, currentCryptor, newCryptor); } - if (getStatus(stateHolder).isBeforeOrEqual(SecretsUpdateStatus.MOVING_DB_DATA)) { + if (getStatus(stateHolder).isBeforeOrEqual(SecretsRotationStatus.MOVING_DB_DATA)) { // Staging files for new database File stagedDbFile = txFiles.stageFile(currentDbFile); @@ -169,7 +169,7 @@ void migrateData( var tempDb = databaseManager.getDatabase(stagedDbFile.getAbsolutePath(), newKey); try { - setStatus(stateHolder, SecretsUpdateStatus.MOVING_DB_DATA); + setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); copyDbData(currentDb, tempDb); } finally { tempDb.close(); @@ -289,9 +289,9 @@ byte[] decryptBlobData(AesCryptor cryptor, byte[] data) * Retrieves the current status from the state holder. * * @param stateHolder The state holder. - * @return The current {@link SecretsUpdateStatus}. + * @return The current {@link SecretsRotationStatus}. */ - SecretsUpdateStatus getStatus(SecretsRotationStateHolder stateHolder) { + SecretsRotationStatus getStatus(SecretsRotationStateHolder stateHolder) { return stateHolder.getState().getStatus(); } @@ -301,7 +301,7 @@ SecretsUpdateStatus getStatus(SecretsRotationStateHolder stateHolder) { * @param stateHolder The state holder. * @param status The new status to set. */ - void setStatus(SecretsRotationStateHolder stateHolder, SecretsUpdateStatus status) { + void setStatus(SecretsRotationStateHolder stateHolder, SecretsRotationStatus status) { stateHolder.setState(stateHolder.getState().setStatus(status)); } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java index 8880430d..ae9ea6e4 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java @@ -20,12 +20,12 @@ public final class SecretsRotationState implements Serializable { @Getter - private SecretsUpdateStatus status; + private SecretsRotationStatus status; @Getter private String transactionId; - public SecretsRotationState setStatus(SecretsUpdateStatus status) { + public SecretsRotationState setStatus(SecretsRotationStatus status) { this.status = status; return this; } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java similarity index 77% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java index 7976fbbe..96da9003 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java @@ -10,7 +10,7 @@ @Getter @RequiredArgsConstructor -public enum SecretsUpdateStatus { +public enum SecretsRotationStatus { INITIALIZING("initializing", 0), MOVING_BLOBS_DATA("moving_blobs_data", 1), MOVING_DB_DATA("moving_db_data", 2), @@ -20,11 +20,11 @@ public enum SecretsUpdateStatus { private final String status; private final int order; - public boolean isBefore(SecretsUpdateStatus other) { + public boolean isBefore(SecretsRotationStatus other) { return this.order < other.order; } - public boolean isBeforeOrEqual(SecretsUpdateStatus other) { + public boolean isBeforeOrEqual(SecretsRotationStatus other) { return this.order <= other.order; } } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java index ca11c175..9d340520 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java @@ -140,7 +140,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt(payloadJson); - SecretsRotationState state = new SecretsRotationState(SecretsUpdateStatus.MOVING_DB_DATA, + SecretsRotationState state = new SecretsRotationState(SecretsRotationStatus.MOVING_DB_DATA, "tx123"); String stateJson = mapper.writeValueAsString(state); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 5e2c985f..92826ce6 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -93,21 +93,21 @@ private CryptoSecrets createNewSecrets() { @Test void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.DONE)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); verify(databaseManager, never()).closeProvider(); verify(txFiles, never()).commit(); - assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should remain DONE if already DONE"); } @Test void testUpdateSecretsAlreadyFailedThrowsException() { when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.FAILED)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( @@ -180,7 +180,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { verify(txFiles).commit(); verify(cryptoManager).setSecrets(eq(context), any(CryptoSecrets.class)); verify(databaseManager).reinitProvider(any()); - assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE after successful migration"); } @@ -189,7 +189,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsUpdateStatus.MOVING_DB_DATA)); + stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); when(cryptoManager.getSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); @@ -200,7 +200,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { verify(cryptoManager).setSecrets(eq(context), any(CryptoSecrets.class)); verify(databaseManager).reinitProvider(any()); - assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE if transaction was already committed"); } @@ -220,7 +220,7 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { "Should throw SecretsUpdateFailedException and trigger rollback on migration failure"); verify(txFiles).rollback(); - assertEquals(SecretsUpdateStatus.FAILED, stateHolder.getState().getStatus(), + assertEquals(SecretsRotationStatus.FAILED, stateHolder.getState().getStatus(), "Status should be FAILED after migration failure"); } @@ -357,9 +357,9 @@ void testDecryptBlobDataWrapsGeneralSecurityException() throws Exception { @Test void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { - secretsUpdateService.setStatus(stateHolder, SecretsUpdateStatus.MOVING_DB_DATA); + secretsUpdateService.setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); - assertEquals(SecretsUpdateStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), + assertEquals(SecretsRotationStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), "Status should be updated in the state holder"); verify(onUpdate).accept(any(SecretsRotationState.class)); } @@ -367,11 +367,11 @@ void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { @Test void testGetStatusReturnsStatusFromStateHolder() { stateHolder.setState(new SecretsRotationState() - .setStatus(SecretsUpdateStatus.MOVING_BLOBS_DATA)); + .setStatus(SecretsRotationStatus.MOVING_BLOBS_DATA)); - SecretsUpdateStatus status = secretsUpdateService.getStatus(stateHolder); + SecretsRotationStatus status = secretsUpdateService.getStatus(stateHolder); - assertEquals(SecretsUpdateStatus.MOVING_BLOBS_DATA, status, + assertEquals(SecretsRotationStatus.MOVING_BLOBS_DATA, status, "Should return the correct status from the state holder"); } From 9c64879fe08db0607f8c680c8fe7f9feaede9b6d Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 17:57:46 +0200 Subject: [PATCH 14/30] Rename SecretsUpdateFailedException to SecretsRotationFailedException --- .../SecretsRotationAndroidService.java | 2 +- .../SecretsRotationFailedException.java | 17 ++++++++++++++++ .../rotation/SecretsRotationService.java | 8 ++++---- .../SecretsUpdateFailedException.java | 17 ---------------- .../SecretsRotationAndroidServiceTest.java | 2 +- .../rotation/SecretsRotationServiceTest.java | 20 +++++++++---------- 6 files changed, 33 insertions(+), 33 deletions(-) create mode 100644 service/src/main/java/app/notesr/service/security/rotation/SecretsRotationFailedException.java delete mode 100644 service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 0227b845..08978c50 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -149,7 +149,7 @@ public void run() { newSecrets); onComplete(); - } catch (SecretsUpdateFailedException | FilesTransactionException e) { + } catch (SecretsRotationFailedException | FilesTransactionException e) { onFail(); Log.e(TAG, "Secrets update failed", e); } finally { diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationFailedException.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationFailedException.java new file mode 100644 index 00000000..db218b47 --- /dev/null +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationFailedException.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 zHd4 + * SPDX-License-Identifier: MIT + */ + +package app.notesr.service.security.rotation; + +public final class SecretsRotationFailedException extends RuntimeException { + + public SecretsRotationFailedException(String message) { + super(message); + } + + public SecretsRotationFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 50492587..c15941cd 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -47,7 +47,7 @@ public final class SecretsRotationService { * @param dbName The name of the database file. * @param stateHolder The state holder for tracking update progress. * @param newSecrets The new crypto secrets to be applied. - * @throws SecretsUpdateFailedException If the secrets update fails. + * @throws SecretsRotationFailedException If the secrets update fails. */ public void updateSecrets( TransactionalFilesUtil txFiles, @@ -59,7 +59,7 @@ public void updateSecrets( try { CryptoSecretsValidator.validate(newSecrets); } catch (IllegalArgumentException e) { - throw new SecretsUpdateFailedException("Invalid new secrets", e); + throw new SecretsRotationFailedException("Invalid new secrets", e); } var currentSecrets = cryptoManager.getSecrets(); @@ -74,7 +74,7 @@ public void updateSecrets( } if (getStatus(stateHolder) == SecretsRotationStatus.FAILED) { - throw new SecretsUpdateFailedException("Secrets update is already failed"); + throw new SecretsRotationFailedException("Secrets update is already failed"); } databaseManager.closeProvider(); @@ -109,7 +109,7 @@ public void updateSecrets( } catch (Exception e) { txFiles.rollback(); setStatus(stateHolder, SecretsRotationStatus.FAILED); - throw new SecretsUpdateFailedException("Secrets update failed", e); + throw new SecretsRotationFailedException("Secrets update failed", e); } finally { currentSecrets.destroy(); newSecrets.destroy(); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java deleted file mode 100644 index e3dc9eff..00000000 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateFailedException.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2026 zHd4 - * SPDX-License-Identifier: MIT - */ - -package app.notesr.service.security.rotation; - -public final class SecretsUpdateFailedException extends RuntimeException { - - public SecretsUpdateFailedException(String message) { - super(message); - } - - public SecretsUpdateFailedException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index 6c100dc2..7e4c4010 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -116,7 +116,7 @@ void testRunFailure() { doNothing().when(service).stopService(); when(txFiles.getTransactionId()).thenReturn("tx-123"); - doThrow(new SecretsUpdateFailedException("Failed")) + doThrow(new SecretsRotationFailedException("Failed")) .when(secretsUpdateService).updateSecrets(any(), any(), any(), any(), any()); service.run(); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 92826ce6..b6a6815b 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -110,7 +110,7 @@ void testUpdateSecretsAlreadyFailedThrowsException() { stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw exception if status is already FAILED"); } @@ -215,7 +215,7 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { doThrow(new RuntimeException("Migration failed")) .when(txFiles).getInternalFile(any(), anyString()); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException and trigger rollback on migration failure"); @@ -379,7 +379,7 @@ void testGetStatusReturnsStatusFromStateHolder() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(null, password.clone()); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets key is null"); } @@ -388,7 +388,7 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(new byte[0], password.clone()); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets key is empty"); } @@ -398,7 +398,7 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyWrongSize() { byte[] wrongSizedKey = new byte[32]; // Wrong size, should be 48 CryptoSecrets newSecrets = new CryptoSecrets(wrongSizedKey, password.clone()); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets key has wrong size"); } @@ -408,7 +408,7 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { byte[] nulledKey = new byte[KEY_SIZE]; // All zeros CryptoSecrets newSecrets = new CryptoSecrets(nulledKey, password.clone()); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets key is all zeros"); } @@ -417,7 +417,7 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), null); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets password is null"); } @@ -426,7 +426,7 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { void testUpdateSecretsThrowsWhenNewSecretsPasswordIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), new char[0]); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets password is empty"); } @@ -436,7 +436,7 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordTooShort() { char[] shortPassword = "abc".toCharArray(); // Less than 4 characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), shortPassword); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets password is too short"); } @@ -446,7 +446,7 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsAllZeros() { char[] nulledPassword = new char[4]; // All '\0' characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), nulledPassword); - assertThrows(SecretsUpdateFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw SecretsUpdateFailedException when new secrets password is all zeros"); } From a3495cfc3e46039ce55d65b8e31a5eae936ba516 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:08:05 +0200 Subject: [PATCH 15/30] Rename secrets update terminology to secrets rotation for consistency --- .../security/rotation/DatabaseManager.java | 2 +- .../SecretsRotationAndroidService.java | 20 +++--- .../rotation/SecretsRotationService.java | 8 +-- .../SecretsRotationAndroidServiceTest.java | 10 +-- .../rotation/SecretsRotationServiceTest.java | 64 +++++++++---------- 5 files changed, 52 insertions(+), 52 deletions(-) diff --git a/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java index f68b23ff..9f1b37d2 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java +++ b/service/src/main/java/app/notesr/service/security/rotation/DatabaseManager.java @@ -9,7 +9,7 @@ /** * Interface for managing database instances and the global database provider state - * during secrets updates. + * during secrets rotation. */ public interface DatabaseManager { AppDatabase getDatabase(String name, byte[] key); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 08978c50..0a422ff1 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -48,18 +48,18 @@ public class SecretsRotationAndroidService extends AndroidService implements Run public static final String NEW_KEY = "new_key"; public static final String PASSWORD = "password"; - public static final String BROADCAST_ACTION = "re_encryption_service_broadcast"; + public static final String BROADCAST_ACTION = "secrets_rotation_service_broadcast"; public static final String EXTRA_CURRENT_STATE = "current_state"; - public static final String EXTRA_COMPLETE = "re_encryption_complete"; - public static final String EXTRA_FAIL = "re_encryption_fail"; - private static final String CHANNEL_ID = "re_encryption_service_channel"; - private static final String CHANNEL_NAME = "Re-encryption Service Channel"; + public static final String EXTRA_COMPLETE = "rotation_completed"; + public static final String EXTRA_FAIL = "rotation_failed"; + private static final String CHANNEL_ID = "secrets_rotation_service"; + private static final String CHANNEL_NAME = "Key Rotation"; private String dbName; private CryptoManager cryptoManager; private SecretsRotationStateHolder stateHolder; private CryptoSecrets newSecrets; - private SecretsRotationService secretsUpdateService; + private SecretsRotationService secretsRotationService; private String encryptedPayload; @Override @@ -71,7 +71,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); stateHolder = new SecretsRotationStateHolder(this::onStateUpdate).setState(state); - secretsUpdateService = getSecretsUpdateService(); + secretsRotationService = getSecretsRotationService(); encryptedPayload = encryptPayload(getPayload()); var thread = new Thread(this); @@ -145,13 +145,13 @@ public void run() { var transactionId = txFiles.getTransactionId(); stateHolder.setState(stateHolder.getState().setTransactionId(transactionId)); - secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, + secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); onComplete(); } catch (SecretsRotationFailedException | FilesTransactionException e) { onFail(); - Log.e(TAG, "Secrets update failed", e); + Log.e(TAG, "Secrets rotation failed", e); } finally { stopService(); } @@ -209,7 +209,7 @@ CryptoSecrets getNewSecrets() { } } - SecretsRotationService getSecretsUpdateService() { + SecretsRotationService getSecretsRotationService() { var context = getApplicationContext(); var databaseManager = new DatabaseManagerImpl(context); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index c15941cd..704c9759 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -45,9 +45,9 @@ public final class SecretsRotationService { * @param txFiles The transactional files utility. * @param cryptoManager The crypto manager instance. * @param dbName The name of the database file. - * @param stateHolder The state holder for tracking update progress. + * @param stateHolder The state holder for tracking rotation progress. * @param newSecrets The new crypto secrets to be applied. - * @throws SecretsRotationFailedException If the secrets update fails. + * @throws SecretsRotationFailedException If the secrets rotation fails. */ public void updateSecrets( TransactionalFilesUtil txFiles, @@ -74,7 +74,7 @@ public void updateSecrets( } if (getStatus(stateHolder) == SecretsRotationStatus.FAILED) { - throw new SecretsRotationFailedException("Secrets update is already failed"); + throw new SecretsRotationFailedException("Secrets rotation is already failed"); } databaseManager.closeProvider(); @@ -109,7 +109,7 @@ public void updateSecrets( } catch (Exception e) { txFiles.rollback(); setStatus(stateHolder, SecretsRotationStatus.FAILED); - throw new SecretsRotationFailedException("Secrets update failed", e); + throw new SecretsRotationFailedException("Secrets rotation failed", e); } finally { currentSecrets.destroy(); newSecrets.destroy(); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index 7e4c4010..05b0a1d8 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -55,7 +55,7 @@ class SecretsRotationAndroidServiceTest { private CryptoManager cryptoManager; @Mock - private SecretsRotationService secretsUpdateService; + private SecretsRotationService secretsRotationService; @Mock private TransactionalFilesUtil txFiles; @@ -79,7 +79,7 @@ void setUp() { // Inject basic dependencies using setters service.setCryptoManager(cryptoManager); service.setNewSecrets(newSecrets); - service.setSecretsUpdateService(secretsUpdateService); + service.setSecretsRotationService(secretsRotationService); service.setDbName("test.db"); } @@ -96,7 +96,7 @@ void testRunSuccess() { service.run(); - verify(secretsUpdateService) + verify(secretsRotationService) .updateSecrets(eq(txFiles), eq(cryptoManager), eq("test.db"), any(), eq(newSecrets)); verify(service).onComplete(); @@ -117,7 +117,7 @@ void testRunFailure() { when(txFiles.getTransactionId()).thenReturn("tx-123"); doThrow(new SecretsRotationFailedException("Failed")) - .when(secretsUpdateService).updateSecrets(any(), any(), any(), any(), any()); + .when(secretsRotationService).updateSecrets(any(), any(), any(), any(), any()); service.run(); @@ -196,7 +196,7 @@ void testOnStartCommand() { .thenReturn(androidServiceRegistry); doReturn(context).when(service).getApplicationContext(); - doReturn(secretsUpdateService).when(service).getSecretsUpdateService(); + doReturn(secretsRotationService).when(service).getSecretsRotationService(); doReturn(newSecrets).when(service).getNewSecrets(); doNothing().when(service).showForegroundNotification(anyInt()); doReturn("encryptedPayload").when(service).encryptPayload(any()); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index b6a6815b..06cc893f 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -62,7 +62,7 @@ class SecretsRotationServiceTest { @Mock private Consumer onUpdate; - private SecretsRotationService secretsUpdateService; + private SecretsRotationService secretsRotationService; private SecretsRotationStateHolder stateHolder; private final String dbName = "test.db"; @@ -72,7 +72,7 @@ class SecretsRotationServiceTest { @BeforeEach void setUp() { - secretsUpdateService = new SecretsRotationService(context, databaseManager); + secretsRotationService = new SecretsRotationService(context, databaseManager); stateHolder = new SecretsRotationStateHolder(onUpdate); // Initialize keys @@ -96,7 +96,7 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); - secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); verify(databaseManager, never()).closeProvider(); verify(txFiles, never()).commit(); @@ -110,7 +110,7 @@ void testUpdateSecretsAlreadyFailedThrowsException() { stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), "Should throw exception if status is already FAILED"); } @@ -174,7 +174,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { return null; }).when(tempDbMock).runInTransaction(any(Callable.class)); - secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); verify(databaseManager).closeProvider(); verify(txFiles).commit(); @@ -194,7 +194,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { when(cryptoManager.getSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); - secretsUpdateService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); verify(txFiles, never()).commit(); verify(cryptoManager).setSecrets(eq(context), any(CryptoSecrets.class)); @@ -215,9 +215,9 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { doThrow(new RuntimeException("Migration failed")) .when(txFiles).getInternalFile(any(), anyString()); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException and trigger rollback on migration failure"); + "Should throw SecretsRotationFailedException and trigger rollback on migration failure"); verify(txFiles).rollback(); assertEquals(SecretsRotationStatus.FAILED, stateHolder.getState().getStatus(), @@ -230,7 +230,7 @@ void testMigrateDataDbAlreadyMigratedReturnsImmediately() throws Exception { when(databaseManager.getDatabase(dbName, newKey)).thenReturn(newDb); when(databaseManager.isDbAvailable(newDb)).thenReturn(true); - secretsUpdateService.migrateData(txFiles, stateHolder, dbName, currentKey, newKey, + secretsRotationService.migrateData(txFiles, stateHolder, dbName, currentKey, newKey, new File("blobs"), mock(AesCryptor.class), mock(AesCryptor.class)); verify(databaseManager, never()).getDatabase(dbName, currentKey); @@ -273,7 +273,7 @@ void testCopyDbDataCopiesAllDataSuccessfully() { return null; }).when(newDb).runInTransaction(any(Callable.class)); - secretsUpdateService.copyDbData(currentDb, newDb); + secretsRotationService.copyDbData(currentDb, newDb); verify(noteDao).insertAll(notes); verify(fileInfoDao).insertAll(fileInfos); @@ -302,7 +302,7 @@ void testUpdateBlobsDataMigratesBlobsSuccessfully() throws Exception { when(currentCryptor.decrypt(oldData)).thenReturn(decryptedData); when(newCryptor.encrypt(decryptedData)).thenReturn(newData); - secretsUpdateService.updateBlobsData(txFiles, oldDb, blobsDir, currentCryptor, newCryptor); + secretsRotationService.updateBlobsData(txFiles, oldDb, blobsDir, currentCryptor, newCryptor); verify(txFiles).writeFileBytes(any(), eq(newData)); } @@ -328,7 +328,7 @@ void testUpdateBlobsDataSkipsAlreadyMigratedBlob() throws Exception { // Successful decryption with new cryptor means it's migrated when(newCryptor.decrypt(migratedData)).thenReturn("plain".getBytes()); - secretsUpdateService.updateBlobsData(txFiles, oldDb, blobsDir, currentCryptor, newCryptor); + secretsRotationService.updateBlobsData(txFiles, oldDb, blobsDir, currentCryptor, newCryptor); verify(currentCryptor, never()).decrypt(any()); verify(newCryptor, never()).encrypt(any()); @@ -340,7 +340,7 @@ void testEncryptBlobDataWrapsGeneralSecurityException() throws Exception { AesCryptor cryptor = mock(AesCryptor.class); when(cryptor.encrypt(any())).thenThrow(new GeneralSecurityException("Encryption failed")); - assertThrows(EncryptionFailedException.class, () -> secretsUpdateService.encryptBlobData( + assertThrows(EncryptionFailedException.class, () -> secretsRotationService.encryptBlobData( cryptor, new byte[0]), "Should wrap GeneralSecurityException in EncryptionFailedException during blob encryption"); } @@ -350,14 +350,14 @@ void testDecryptBlobDataWrapsGeneralSecurityException() throws Exception { AesCryptor cryptor = mock(AesCryptor.class); when(cryptor.decrypt(any())).thenThrow(new GeneralSecurityException("Decryption failed")); - assertThrows(DecryptionFailedException.class, () -> secretsUpdateService.decryptBlobData( + assertThrows(DecryptionFailedException.class, () -> secretsRotationService.decryptBlobData( cryptor, new byte[0]), "Should wrap GeneralSecurityException in DecryptionFailedException during blob decryption"); } @Test void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { - secretsUpdateService.setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); + secretsRotationService.setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); assertEquals(SecretsRotationStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), "Status should be updated in the state holder"); @@ -369,7 +369,7 @@ void testGetStatusReturnsStatusFromStateHolder() { stateHolder.setState(new SecretsRotationState() .setStatus(SecretsRotationStatus.MOVING_BLOBS_DATA)); - SecretsRotationStatus status = secretsUpdateService.getStatus(stateHolder); + SecretsRotationStatus status = secretsRotationService.getStatus(stateHolder); assertEquals(SecretsRotationStatus.MOVING_BLOBS_DATA, status, "Should return the correct status from the state holder"); @@ -379,18 +379,18 @@ void testGetStatusReturnsStatusFromStateHolder() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(null, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets key is null"); + "Should throw SecretsRotationFailedException when new secrets key is null"); } @Test void testUpdateSecretsThrowsWhenNewSecretsKeyIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(new byte[0], password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets key is empty"); + "Should throw SecretsRotationFailedException when new secrets key is empty"); } @Test @@ -398,9 +398,9 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyWrongSize() { byte[] wrongSizedKey = new byte[32]; // Wrong size, should be 48 CryptoSecrets newSecrets = new CryptoSecrets(wrongSizedKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets key has wrong size"); + "Should throw SecretsRotationFailedException when new secrets key has wrong size"); } @Test @@ -408,27 +408,27 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { byte[] nulledKey = new byte[KEY_SIZE]; // All zeros CryptoSecrets newSecrets = new CryptoSecrets(nulledKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets key is all zeros"); + "Should throw SecretsRotationFailedException when new secrets key is all zeros"); } @Test void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), null); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets password is null"); + "Should throw SecretsRotationFailedException when new secrets password is null"); } @Test void testUpdateSecretsThrowsWhenNewSecretsPasswordIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), new char[0]); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets password is empty"); + "Should throw SecretsRotationFailedException when new secrets password is empty"); } @Test @@ -436,9 +436,9 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordTooShort() { char[] shortPassword = "abc".toCharArray(); // Less than 4 characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), shortPassword); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets password is too short"); + "Should throw SecretsRotationFailedException when new secrets password is too short"); } @Test @@ -446,8 +446,8 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsAllZeros() { char[] nulledPassword = new char[4]; // All '\0' characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), nulledPassword); - assertThrows(SecretsRotationFailedException.class, () -> secretsUpdateService.updateSecrets( + assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsUpdateFailedException when new secrets password is all zeros"); + "Should throw SecretsRotationFailedException when new secrets password is all zeros"); } } From b30d9cde52fb2be3525bc40d7e64756e533e60a1 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:05:49 +0200 Subject: [PATCH 16/30] Refactor secrets rotation classes to use AppSecurityService instead of CryptoManager --- .../SecretsRotationAndroidService.java | 11 +- .../rotation/SecretsRotationService.java | 19 +-- .../SecretsRotationAndroidServiceTest.java | 20 +-- .../rotation/SecretsRotationServiceTest.java | 119 +++++++++++------- 4 files changed, 101 insertions(+), 68 deletions(-) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 0a422ff1..52ad5be2 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -27,8 +27,6 @@ import java.nio.charset.StandardCharsets; import app.notesr.core.security.SecretCache; -import app.notesr.core.security.crypto.CryptoManager; -import app.notesr.core.security.crypto.CryptoManagerProvider; import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.core.util.FilesTransactionException; @@ -38,6 +36,7 @@ import app.notesr.service.AndroidService; import app.notesr.service.AndroidServiceEntry; import app.notesr.service.AndroidServiceRegistry; +import app.notesr.service.security.AppSecurityService; import lombok.AccessLevel; import lombok.Setter; @@ -56,7 +55,7 @@ public class SecretsRotationAndroidService extends AndroidService implements Run private static final String CHANNEL_NAME = "Key Rotation"; private String dbName; - private CryptoManager cryptoManager; + private AppSecurityService appSecurityService; private SecretsRotationStateHolder stateHolder; private CryptoSecrets newSecrets; private SecretsRotationService secretsRotationService; @@ -66,7 +65,7 @@ public class SecretsRotationAndroidService extends AndroidService implements Run public int onStartCommand(Intent intent, int flags, int startId) { dbName = DatabaseProvider.DB_NAME; - cryptoManager = CryptoManagerProvider.getInstance(getApplicationContext()); + appSecurityService = new AppSecurityService(getApplicationContext()); newSecrets = getNewSecrets(); var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); @@ -121,7 +120,7 @@ private SecretsRotationAndroidServiceStarter.Payload getPayload() { } String encryptPayload(SecretsRotationAndroidServiceStarter.Payload payload) { - return getEncryptedJson(new ObjectMapper(), payload, cryptoManager.getSecrets()); + return getEncryptedJson(new ObjectMapper(), payload, appSecurityService.getActualSecrets()); } String serializeState(SecretsRotationState state) { @@ -145,7 +144,7 @@ public void run() { var transactionId = txFiles.getTransactionId(); stateHolder.setState(stateHolder.getState().setTransactionId(transactionId)); - secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, + secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, stateHolder, newSecrets); onComplete(); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 704c9759..97cc7d29 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -14,7 +14,6 @@ import app.notesr.core.security.crypto.AesCryptor; import app.notesr.core.security.crypto.AesCryptorFactory; -import app.notesr.core.security.crypto.CryptoManager; import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.core.security.exception.DecryptionFailedException; import app.notesr.core.security.exception.EncryptionFailedException; @@ -23,6 +22,7 @@ import app.notesr.data.AppDatabase; import app.notesr.data.model.FileBlobInfo; import app.notesr.service.file.FileService; +import app.notesr.service.security.AppSecurityService; import lombok.RequiredArgsConstructor; /** @@ -42,16 +42,17 @@ public final class SecretsRotationService { *

* It performs a migration of the database and file blobs to the new encryption settings. * - * @param txFiles The transactional files utility. - * @param cryptoManager The crypto manager instance. - * @param dbName The name of the database file. - * @param stateHolder The state holder for tracking rotation progress. - * @param newSecrets The new crypto secrets to be applied. + * @param txFiles The transactional files utility. + * @param appSecurityService The application security service for managing secrets. + * @param dbName The name of the database file. + * @param stateHolder The state holder for tracking rotation progress. + * @param newSecrets The new crypto secrets to be applied. + * * @throws SecretsRotationFailedException If the secrets rotation fails. */ public void updateSecrets( TransactionalFilesUtil txFiles, - CryptoManager cryptoManager, + AppSecurityService appSecurityService, String dbName, SecretsRotationStateHolder stateHolder, CryptoSecrets newSecrets) { @@ -62,7 +63,7 @@ public void updateSecrets( throw new SecretsRotationFailedException("Invalid new secrets", e); } - var currentSecrets = cryptoManager.getSecrets(); + var currentSecrets = appSecurityService.getActualSecrets(); try (txFiles) { if (getStatus(stateHolder) == null) { @@ -102,7 +103,7 @@ public void updateSecrets( } } - cryptoManager.setSecrets(context, newSecrets); + appSecurityService.setSecrets(newSecrets); setStatus(stateHolder, SecretsRotationStatus.DONE); databaseManager.reinitProvider(newSecrets.getKey()); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index 05b0a1d8..cf19dd21 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -36,11 +36,11 @@ import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; -import app.notesr.core.security.crypto.CryptoManager; import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.core.util.TransactionalFilesUtil; import app.notesr.service.AndroidServiceEntry; import app.notesr.service.AndroidServiceRegistry; +import app.notesr.service.security.AppSecurityService; @ExtendWith(MockitoExtension.class) class SecretsRotationAndroidServiceTest { @@ -52,7 +52,7 @@ class SecretsRotationAndroidServiceTest { private Intent intent; @Mock - private CryptoManager cryptoManager; + private AppSecurityService appSecurityService; @Mock private SecretsRotationService secretsRotationService; @@ -77,7 +77,7 @@ void setUp() { newSecrets = new CryptoSecrets(new byte[32], "password".toCharArray()); // Inject basic dependencies using setters - service.setCryptoManager(cryptoManager); + service.setAppSecurityService(appSecurityService); service.setNewSecrets(newSecrets); service.setSecretsRotationService(secretsRotationService); service.setDbName("test.db"); @@ -97,7 +97,7 @@ void testRunSuccess() { service.run(); verify(secretsRotationService) - .updateSecrets(eq(txFiles), eq(cryptoManager), eq("test.db"), any(), + .updateSecrets(eq(txFiles), eq(appSecurityService), eq("test.db"), any(), eq(newSecrets)); verify(service).onComplete(); verify(service).stopService(); @@ -142,7 +142,9 @@ void testOnFailCallsSendBroadcast() { @Test void testSendUpdateBroadcast() { - try (MockedStatic mockedStatic = mockStatic(LocalBroadcastManager.class)) { + MockedStatic mockedStatic = mockStatic(LocalBroadcastManager.class); + + try (mockedStatic) { mockedStatic.when(() -> LocalBroadcastManager.getInstance(any())) .thenReturn(localBroadcastManager); @@ -162,7 +164,9 @@ void testSendUpdateBroadcast() { @Test void testOnStateUpdateUpdatesRegistry() { - try (MockedStatic mockedStatic = mockStatic(AndroidServiceRegistry.class)) { + MockedStatic mockedStatic = mockStatic(AndroidServiceRegistry.class); + + try (mockedStatic) { mockedStatic.when(() -> AndroidServiceRegistry.getInstance(any())) .thenReturn(androidServiceRegistry); @@ -191,7 +195,9 @@ void testOnStartCommand() { when(intent.getSerializableExtra(SecretsRotationAndroidService.EXTRA_CURRENT_STATE)) .thenReturn(state); - try (MockedStatic registryMock = mockStatic(AndroidServiceRegistry.class)) { + MockedStatic registryMock = mockStatic(AndroidServiceRegistry.class); + + try (registryMock) { registryMock.when(() -> AndroidServiceRegistry.getInstance(any())) .thenReturn(androidServiceRegistry); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 06cc893f..0af5a27c 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -33,7 +33,6 @@ import java.util.function.Consumer; import app.notesr.core.security.crypto.AesCryptor; -import app.notesr.core.security.crypto.CryptoManager; import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.core.security.exception.DecryptionFailedException; import app.notesr.core.security.exception.EncryptionFailedException; @@ -45,6 +44,7 @@ import app.notesr.data.model.FileBlobInfo; import app.notesr.data.model.FileInfo; import app.notesr.data.model.Note; +import app.notesr.service.security.AppSecurityService; @ExtendWith(MockitoExtension.class) class SecretsRotationServiceTest { @@ -53,12 +53,16 @@ class SecretsRotationServiceTest { @Mock private Context context; + @Mock private DatabaseManager databaseManager; + @Mock private TransactionalFilesUtil txFiles; + @Mock - private CryptoManager cryptoManager; + private AppSecurityService appSecurityService; + @Mock private Consumer onUpdate; @@ -92,11 +96,12 @@ private CryptoSecrets createNewSecrets() { @Test void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { - when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); + when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); - secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets); verify(databaseManager, never()).closeProvider(); verify(txFiles, never()).commit(); @@ -106,12 +111,13 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { @Test void testUpdateSecretsAlreadyFailedThrowsException() { - when(cryptoManager.getSecrets()).thenReturn(createCurrentSecrets()); + when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, + dbName, stateHolder, newSecrets), "Should throw exception if status is already FAILED"); } @@ -120,7 +126,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); - when(cryptoManager.getSecrets()).thenReturn(currentSecrets); + when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(false); AppDatabase newDbMock = mock(AppDatabase.class); @@ -174,11 +180,12 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { return null; }).when(tempDbMock).runInTransaction(any(Callable.class)); - secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets); verify(databaseManager).closeProvider(); verify(txFiles).commit(); - verify(cryptoManager).setSecrets(eq(context), any(CryptoSecrets.class)); + verify(appSecurityService).setSecrets(any(CryptoSecrets.class)); verify(databaseManager).reinitProvider(any()); assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE after successful migration"); @@ -189,15 +196,17 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); + stateHolder.setState( + new SecretsRotationState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); - when(cryptoManager.getSecrets()).thenReturn(currentSecrets); + when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); - secretsRotationService.updateSecrets(txFiles, cryptoManager, dbName, stateHolder, newSecrets); + secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets); verify(txFiles, never()).commit(); - verify(cryptoManager).setSecrets(eq(context), any(CryptoSecrets.class)); + verify(appSecurityService).setSecrets(any(CryptoSecrets.class)); verify(databaseManager).reinitProvider(any()); assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), @@ -209,15 +218,17 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); - when(cryptoManager.getSecrets()).thenReturn(currentSecrets); + when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(false); doThrow(new RuntimeException("Migration failed")) .when(txFiles).getInternalFile(any(), anyString()); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException and trigger rollback on migration failure"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, + dbName, stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " and trigger rollback on migration failure"); verify(txFiles).rollback(); assertEquals(SecretsRotationStatus.FAILED, stateHolder.getState().getStatus(), @@ -328,7 +339,8 @@ void testUpdateBlobsDataSkipsAlreadyMigratedBlob() throws Exception { // Successful decryption with new cryptor means it's migrated when(newCryptor.decrypt(migratedData)).thenReturn("plain".getBytes()); - secretsRotationService.updateBlobsData(txFiles, oldDb, blobsDir, currentCryptor, newCryptor); + secretsRotationService.updateBlobsData(txFiles, oldDb, blobsDir, + currentCryptor, newCryptor); verify(currentCryptor, never()).decrypt(any()); verify(newCryptor, never()).encrypt(any()); @@ -340,9 +352,10 @@ void testEncryptBlobDataWrapsGeneralSecurityException() throws Exception { AesCryptor cryptor = mock(AesCryptor.class); when(cryptor.encrypt(any())).thenThrow(new GeneralSecurityException("Encryption failed")); - assertThrows(EncryptionFailedException.class, () -> secretsRotationService.encryptBlobData( - cryptor, new byte[0]), - "Should wrap GeneralSecurityException in EncryptionFailedException during blob encryption"); + assertThrows(EncryptionFailedException.class, + () -> secretsRotationService.encryptBlobData(cryptor, new byte[0]), + "Should wrap GeneralSecurityException in EncryptionFailedException" + + " during blob encryption"); } @Test @@ -350,9 +363,10 @@ void testDecryptBlobDataWrapsGeneralSecurityException() throws Exception { AesCryptor cryptor = mock(AesCryptor.class); when(cryptor.decrypt(any())).thenThrow(new GeneralSecurityException("Decryption failed")); - assertThrows(DecryptionFailedException.class, () -> secretsRotationService.decryptBlobData( - cryptor, new byte[0]), - "Should wrap GeneralSecurityException in DecryptionFailedException during blob decryption"); + assertThrows(DecryptionFailedException.class, + () -> secretsRotationService.decryptBlobData(cryptor, new byte[0]), + "Should wrap GeneralSecurityException in DecryptionFailedException" + + " during blob decryption"); } @Test @@ -379,8 +393,9 @@ void testGetStatusReturnsStatusFromStateHolder() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(null, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), "Should throw SecretsRotationFailedException when new secrets key is null"); } @@ -388,8 +403,9 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(new byte[0], password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), "Should throw SecretsRotationFailedException when new secrets key is empty"); } @@ -398,9 +414,11 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyWrongSize() { byte[] wrongSizedKey = new byte[32]; // Wrong size, should be 48 CryptoSecrets newSecrets = new CryptoSecrets(wrongSizedKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets key has wrong size"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets key has wrong size"); } @Test @@ -408,9 +426,11 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { byte[] nulledKey = new byte[KEY_SIZE]; // All zeros CryptoSecrets newSecrets = new CryptoSecrets(nulledKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets key is all zeros"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets key is all zeros"); } @Test @@ -418,17 +438,20 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), null); assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets password is null"); + txFiles, appSecurityService, dbName, stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets password is null"); } @Test void testUpdateSecretsThrowsWhenNewSecretsPasswordIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), new char[0]); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets password is empty"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets password is empty"); } @Test @@ -436,9 +459,11 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordTooShort() { char[] shortPassword = "abc".toCharArray(); // Less than 4 characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), shortPassword); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets password is too short"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets password is too short"); } @Test @@ -446,8 +471,10 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsAllZeros() { char[] nulledPassword = new char[4]; // All '\0' characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), nulledPassword); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, cryptoManager, dbName, stateHolder, newSecrets), - "Should throw SecretsRotationFailedException when new secrets password is all zeros"); + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + stateHolder, newSecrets), + "Should throw SecretsRotationFailedException" + + " when new secrets password is all zeros"); } } From 6bc2469a9c6feb762b71e9171dbcb24ffa0e2163 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:13:56 +0200 Subject: [PATCH 17/30] Fix SecretsRotationService to use copy of new secrets for `AppSecurityService.setSecrets` --- .../service/security/rotation/SecretsRotationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 97cc7d29..e564e311 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -103,7 +103,7 @@ public void updateSecrets( } } - appSecurityService.setSecrets(newSecrets); + appSecurityService.setSecrets(CryptoSecrets.from(newSecrets)); setStatus(stateHolder, SecretsRotationStatus.DONE); databaseManager.reinitProvider(newSecrets.getKey()); From 0ff9f373c955e0219cc23c7271dab408856db5e7 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Thu, 30 Jul 2026 11:14:28 +0200 Subject: [PATCH 18/30] Verify CryptoSecrets are copied when updating secrets in SecretsRotationServiceTest --- .../rotation/SecretsRotationServiceTest.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 0af5a27c..321bd61e 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -22,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -125,6 +128,7 @@ void testUpdateSecretsAlreadyFailedThrowsException() { void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); + byte[] expectedNewKey = newKey.clone(); when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(false); @@ -185,7 +189,16 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { verify(databaseManager).closeProvider(); verify(txFiles).commit(); - verify(appSecurityService).setSecrets(any(CryptoSecrets.class)); + + var secretsCaptor = ArgumentCaptor.forClass(CryptoSecrets.class); + verify(appSecurityService).setSecrets(secretsCaptor.capture()); + + CryptoSecrets passedSecrets = secretsCaptor.getValue(); + assertNotSame(newSecrets, passedSecrets, + "setSecrets should receive a copy, not the original instance"); + assertArrayEquals(expectedNewKey, passedSecrets.getKey(), + "Copied secrets must preserve key bytes"); + verify(databaseManager).reinitProvider(any()); assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE after successful migration"); @@ -195,6 +208,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { CryptoSecrets currentSecrets = createCurrentSecrets(); CryptoSecrets newSecrets = createNewSecrets(); + byte[] expectedNewKey = newKey.clone(); stateHolder.setState( new SecretsRotationState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); @@ -206,7 +220,16 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { stateHolder, newSecrets); verify(txFiles, never()).commit(); - verify(appSecurityService).setSecrets(any(CryptoSecrets.class)); + + var secretsCaptor = ArgumentCaptor.forClass(CryptoSecrets.class); + verify(appSecurityService).setSecrets(secretsCaptor.capture()); + + CryptoSecrets passedSecrets = secretsCaptor.getValue(); + assertNotSame(newSecrets, passedSecrets, + "setSecrets should receive a copy, not the original instance"); + assertArrayEquals(expectedNewKey, passedSecrets.getKey(), + "Copied secrets must preserve key bytes"); + verify(databaseManager).reinitProvider(any()); assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), From ebf0f20efd917877172808dee3b862ad354cb8e3 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:42:22 +0200 Subject: [PATCH 19/30] Add @EqualsAndHashCode to CryptoSecrets DTO --- .../main/java/app/notesr/core/security/dto/CryptoSecrets.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/app/notesr/core/security/dto/CryptoSecrets.java b/core/src/main/java/app/notesr/core/security/dto/CryptoSecrets.java index 862af912..4e6ee485 100644 --- a/core/src/main/java/app/notesr/core/security/dto/CryptoSecrets.java +++ b/core/src/main/java/app/notesr/core/security/dto/CryptoSecrets.java @@ -7,10 +7,9 @@ import java.util.Arrays; -import app.notesr.core.util.CharUtils; -import app.notesr.core.util.KeyUtils; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; /** * Data transfer object containing cryptographic secrets. @@ -20,6 +19,7 @@ */ @AllArgsConstructor @Data +@EqualsAndHashCode public final class CryptoSecrets { public static final int MASTER_KEY_SIZE = 48; From 8953c16d8e87e6f260ac9bf79550d1011a2719c6 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:44:23 +0200 Subject: [PATCH 20/30] Refactor secret rotation service and add password update functionality - Add `updatePassword` method to `SecretsRotationService` for standalone password changes - Update `SecretsRotationService` to ensure `CryptoSecrets` are destroyed after use to improve memory safety - Refactor dependency injection to pass `DatabaseManager` directly to rotation methods - Use `IllegalArgumentException` for secret validation failures - Update `SecretsRotationAndroidService` and tests to reflect the new service interface --- .../SecretsRotationAndroidService.java | 17 +- .../rotation/SecretsRotationService.java | 65 ++++++- .../SecretsRotationAndroidServiceTest.java | 9 +- .../rotation/SecretsRotationServiceTest.java | 183 +++++++++++++++--- 4 files changed, 223 insertions(+), 51 deletions(-) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java index 52ad5be2..ffda9d57 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java @@ -55,22 +55,26 @@ public class SecretsRotationAndroidService extends AndroidService implements Run private static final String CHANNEL_NAME = "Key Rotation"; private String dbName; + private DatabaseManager databaseManager; private AppSecurityService appSecurityService; + private SecretsRotationService secretsRotationService; private SecretsRotationStateHolder stateHolder; private CryptoSecrets newSecrets; - private SecretsRotationService secretsRotationService; private String encryptedPayload; @Override public int onStartCommand(Intent intent, int flags, int startId) { dbName = DatabaseProvider.DB_NAME; + databaseManager = new DatabaseManagerImpl(getApplicationContext()); appSecurityService = new AppSecurityService(getApplicationContext()); + secretsRotationService = new SecretsRotationService(getApplicationContext(), + appSecurityService); + newSecrets = getNewSecrets(); var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); stateHolder = new SecretsRotationStateHolder(this::onStateUpdate).setState(state); - secretsRotationService = getSecretsRotationService(); encryptedPayload = encryptPayload(getPayload()); var thread = new Thread(this); @@ -144,7 +148,7 @@ public void run() { var transactionId = txFiles.getTransactionId(); stateHolder.setState(stateHolder.getState().setTransactionId(transactionId)); - secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, stateHolder, + secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets); onComplete(); @@ -207,11 +211,4 @@ CryptoSecrets getNewSecrets() { throw new RuntimeException(e); } } - - SecretsRotationService getSecretsRotationService() { - var context = getApplicationContext(); - var databaseManager = new DatabaseManagerImpl(context); - - return new SecretsRotationService(context, databaseManager); - } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index e564e311..679fc9ae 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -35,24 +35,63 @@ public final class SecretsRotationService { private final Context context; - private final DatabaseManager databaseManager; + private final AppSecurityService appSecurityService; + + /** + * Updates the password in the crypto secrets. + * After the update, the new password are securely cleared to minimize sensitive data + * exposure in memory. + * + * @param newPassword The new password to set. + * @throws IllegalArgumentException If the new password is invalid. + * @throws SecretsRotationFailedException If the password update fails. + */ + public void updatePassword(char[] newPassword) { + try { + CryptoSecretsValidator.validatePassword(newPassword); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid password", e); + } + + CryptoSecrets currentSecrets = null; + + try { + currentSecrets = appSecurityService.getActualSecrets(); + currentSecrets.setPassword(newPassword); + appSecurityService.setSecrets(CryptoSecrets.from(currentSecrets)); + } catch (Exception e) { + throw new SecretsRotationFailedException("Failed to update password", e); + } finally { + if (currentSecrets != null) { + // Also fills the new password with \0 + currentSecrets.destroy(); + } + } + } /** * Updates the crypto secrets (master key and password) and migrates all encrypted data. + * This could be heavy and long-term operation, so it should be executed + * using {@link SecretsRotationAndroidServiceStarter}. *

* It performs a migration of the database and file blobs to the new encryption settings. + * After the migration, the newSecrets are destroyed to minimize sensitive data + * exposure in memory. * * @param txFiles The transactional files utility. - * @param appSecurityService The application security service for managing secrets. + * @param databaseManager The database manager for handling database operations. * @param dbName The name of the database file. * @param stateHolder The state holder for tracking rotation progress. * @param newSecrets The new crypto secrets to be applied. * + * @throws IllegalArgumentException If the new secrets are invalid. * @throws SecretsRotationFailedException If the secrets rotation fails. + * @see SecretsRotationAndroidService + * @see SecretsRotationAndroidServiceStarter */ public void updateSecrets( TransactionalFilesUtil txFiles, - AppSecurityService appSecurityService, + DatabaseManager databaseManager, String dbName, SecretsRotationStateHolder stateHolder, CryptoSecrets newSecrets) { @@ -60,12 +99,14 @@ public void updateSecrets( try { CryptoSecretsValidator.validate(newSecrets); } catch (IllegalArgumentException e) { - throw new SecretsRotationFailedException("Invalid new secrets", e); + throw new IllegalArgumentException("Invalid new secrets", e); } - var currentSecrets = appSecurityService.getActualSecrets(); + CryptoSecrets currentSecrets = null; try (txFiles) { + currentSecrets = appSecurityService.getActualSecrets(); + if (getStatus(stateHolder) == null) { setStatus(stateHolder, SecretsRotationStatus.INITIALIZING); } @@ -81,12 +122,13 @@ public void updateSecrets( databaseManager.closeProvider(); if (!txFiles.isCommitted()) { - var currentCryptor = AesCryptorFactory.createAesGcmCryptor(currentSecrets); - var newCryptor = AesCryptorFactory.createAesGcmCryptor(newSecrets); - var currentBlobsDir = txFiles.getInternalFile(context, FileService.BLOBS_DIR_NAME); + AesCryptor currentCryptor = AesCryptorFactory.createAesGcmCryptor(currentSecrets); + AesCryptor newCryptor = AesCryptorFactory.createAesGcmCryptor(newSecrets); + File currentBlobsDir = txFiles.getInternalFile(context, FileService.BLOBS_DIR_NAME); migrateData( txFiles, + databaseManager, stateHolder, dbName, currentSecrets.getKey(), @@ -112,7 +154,10 @@ public void updateSecrets( setStatus(stateHolder, SecretsRotationStatus.FAILED); throw new SecretsRotationFailedException("Secrets rotation failed", e); } finally { - currentSecrets.destroy(); + if (currentSecrets != null) { + currentSecrets.destroy(); + } + newSecrets.destroy(); } } @@ -121,6 +166,7 @@ public void updateSecrets( * Migrates the database and file blobs from the current encryption settings to the new ones. * * @param txFiles The transactional files utility. + * @param databaseManager The database manager for handling database operations. * @param stateHolder The state holder for the update process. * @param dbName The name of the database to migrate. * @param currentKey The current database encryption key. @@ -134,6 +180,7 @@ public void updateSecrets( */ void migrateData( TransactionalFilesUtil txFiles, + DatabaseManager databaseManager, SecretsRotationStateHolder stateHolder, String dbName, byte[] currentKey, diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java index cf19dd21..3c34de52 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java @@ -54,6 +54,9 @@ class SecretsRotationAndroidServiceTest { @Mock private AppSecurityService appSecurityService; + @Mock + private DatabaseManager databaseManager; + @Mock private SecretsRotationService secretsRotationService; @@ -78,8 +81,9 @@ void setUp() { // Inject basic dependencies using setters service.setAppSecurityService(appSecurityService); - service.setNewSecrets(newSecrets); + service.setDatabaseManager(databaseManager); service.setSecretsRotationService(secretsRotationService); + service.setNewSecrets(newSecrets); service.setDbName("test.db"); } @@ -97,7 +101,7 @@ void testRunSuccess() { service.run(); verify(secretsRotationService) - .updateSecrets(eq(txFiles), eq(appSecurityService), eq("test.db"), any(), + .updateSecrets(eq(txFiles), eq(databaseManager), eq("test.db"), any(), eq(newSecrets)); verify(service).onComplete(); verify(service).stopService(); @@ -202,7 +206,6 @@ void testOnStartCommand() { .thenReturn(androidServiceRegistry); doReturn(context).when(service).getApplicationContext(); - doReturn(secretsRotationService).when(service).getSecretsRotationService(); doReturn(newSecrets).when(service).getNewSecrets(); doNothing().when(service).showForegroundNotification(anyInt()); doReturn("encryptedPayload").when(service).encryptPayload(any()); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index 321bd61e..bed9b7a8 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -79,7 +80,7 @@ class SecretsRotationServiceTest { @BeforeEach void setUp() { - secretsRotationService = new SecretsRotationService(context, databaseManager); + secretsRotationService = new SecretsRotationService(context, appSecurityService); stateHolder = new SecretsRotationStateHolder(onUpdate); // Initialize keys @@ -103,7 +104,7 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); - secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets); verify(databaseManager, never()).closeProvider(); @@ -119,15 +120,16 @@ void testUpdateSecretsAlreadyFailedThrowsException() { CryptoSecrets newSecrets = createNewSecrets(); assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw exception if status is already FAILED"); } @Test void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { - CryptoSecrets currentSecrets = createCurrentSecrets(); - CryptoSecrets newSecrets = createNewSecrets(); + CryptoSecrets currentSecrets = spy(createCurrentSecrets()); + CryptoSecrets newSecrets = spy(createNewSecrets()); + byte[] expectedNewKey = newKey.clone(); when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); @@ -184,7 +186,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { return null; }).when(tempDbMock).runInTransaction(any(Callable.class)); - secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets); verify(databaseManager).closeProvider(); @@ -202,12 +204,15 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { verify(databaseManager).reinitProvider(any()); assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE after successful migration"); + + verify(currentSecrets).destroy(); + verify(newSecrets).destroy(); } @Test void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { - CryptoSecrets currentSecrets = createCurrentSecrets(); - CryptoSecrets newSecrets = createNewSecrets(); + CryptoSecrets currentSecrets = spy(createCurrentSecrets()); + CryptoSecrets newSecrets = spy(createNewSecrets()); byte[] expectedNewKey = newKey.clone(); stateHolder.setState( @@ -216,7 +221,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); - secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets); verify(txFiles, never()).commit(); @@ -234,12 +239,15 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE if transaction was already committed"); + + verify(currentSecrets).destroy(); + verify(newSecrets).destroy(); } @Test void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { - CryptoSecrets currentSecrets = createCurrentSecrets(); - CryptoSecrets newSecrets = createNewSecrets(); + CryptoSecrets currentSecrets = spy(createCurrentSecrets()); + CryptoSecrets newSecrets = spy(createNewSecrets()); when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(false); @@ -248,12 +256,16 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { .when(txFiles).getInternalFile(any(), anyString()); assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " and trigger rollback on migration failure"); verify(txFiles).rollback(); + verify(databaseManager).closeProvider(); + verify(currentSecrets).destroy(); + verify(newSecrets).destroy(); + assertEquals(SecretsRotationStatus.FAILED, stateHolder.getState().getStatus(), "Status should be FAILED after migration failure"); } @@ -264,7 +276,8 @@ void testMigrateDataDbAlreadyMigratedReturnsImmediately() throws Exception { when(databaseManager.getDatabase(dbName, newKey)).thenReturn(newDb); when(databaseManager.isDbAvailable(newDb)).thenReturn(true); - secretsRotationService.migrateData(txFiles, stateHolder, dbName, currentKey, newKey, + secretsRotationService.migrateData(txFiles, databaseManager, + stateHolder, dbName, currentKey, newKey, new File("blobs"), mock(AesCryptor.class), mock(AesCryptor.class)); verify(databaseManager, never()).getDatabase(dbName, currentKey); @@ -416,8 +429,8 @@ void testGetStatusReturnsStatusFromStateHolder() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(null, password.clone()); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException when new secrets key is null"); } @@ -426,8 +439,8 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsNull() { void testUpdateSecretsThrowsWhenNewSecretsKeyIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(new byte[0], password.clone()); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException when new secrets key is empty"); } @@ -437,8 +450,8 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyWrongSize() { byte[] wrongSizedKey = new byte[32]; // Wrong size, should be 48 CryptoSecrets newSecrets = new CryptoSecrets(wrongSizedKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets key has wrong size"); @@ -449,8 +462,8 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { byte[] nulledKey = new byte[KEY_SIZE]; // All zeros CryptoSecrets newSecrets = new CryptoSecrets(nulledKey, password.clone()); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets key is all zeros"); @@ -460,8 +473,8 @@ void testUpdateSecretsThrowsWhenNewSecretsKeyIsAllZeros() { void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), null); - assertThrows(SecretsRotationFailedException.class, () -> secretsRotationService.updateSecrets( - txFiles, appSecurityService, dbName, stateHolder, newSecrets), + assertThrows(IllegalArgumentException.class, () -> secretsRotationService.updateSecrets( + txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets password is null"); } @@ -470,8 +483,8 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsNull() { void testUpdateSecretsThrowsWhenNewSecretsPasswordIsEmpty() { CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), new char[0]); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets password is empty"); @@ -482,8 +495,8 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordTooShort() { char[] shortPassword = "abc".toCharArray(); // Less than 4 characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), shortPassword); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets password is too short"); @@ -494,10 +507,122 @@ void testUpdateSecretsThrowsWhenNewSecretsPasswordIsAllZeros() { char[] nulledPassword = new char[4]; // All '\0' characters CryptoSecrets newSecrets = new CryptoSecrets(newKey.clone(), nulledPassword); - assertThrows(SecretsRotationFailedException.class, - () -> secretsRotationService.updateSecrets(txFiles, appSecurityService, dbName, + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, stateHolder, newSecrets), "Should throw SecretsRotationFailedException" + " when new secrets password is all zeros"); } + + @Test + void testUpdateSecretsDestroysNewSecretsEvenWhenCurrentSecretsIsNull() { + CryptoSecrets newSecrets = spy(createNewSecrets()); + + when(appSecurityService.getActualSecrets()).thenReturn(null); + when(txFiles.isCommitted()).thenReturn(false); + + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updateSecrets(txFiles, databaseManager, + dbName, stateHolder, newSecrets), + "Should throw SecretsRotationFailedException and still destroy newSecrets"); + + verify(newSecrets).destroy(); + } + + @Test + void testUpdatePasswordSuccessfully() { + CryptoSecrets currentSecrets = spy(createCurrentSecrets()); + char[] newPassword = "newPassword123".toCharArray(); + + when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); + + secretsRotationService.updatePassword(newPassword.clone()); + + var secretsCaptor = ArgumentCaptor.forClass(CryptoSecrets.class); + verify(appSecurityService).setSecrets(secretsCaptor.capture()); + + CryptoSecrets passedSecrets = secretsCaptor.getValue(); + assertArrayEquals(newPassword, passedSecrets.getPassword(), + "Passed secrets should contain the new password"); + + verify(currentSecrets).destroy(); + } + + @Test + void testUpdatePasswordThrowsWhenPasswordIsNull() { + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updatePassword(null), + "Should throw IllegalArgumentException when password is null"); + } + + @Test + void testUpdatePasswordThrowsWhenPasswordIsEmpty() { + char[] emptyPassword = new char[0]; + + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updatePassword(emptyPassword), + "Should throw IllegalArgumentException when password is empty"); + } + + @Test + void testUpdatePasswordThrowsWhenPasswordIsTooShort() { + char[] shortPassword = "abc".toCharArray(); // Less than 4 characters + + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updatePassword(shortPassword), + "Should throw IllegalArgumentException when password is too short"); + } + + @Test + void testUpdatePasswordThrowsWhenPasswordIsAllZeros() { + char[] nulledPassword = new char[4]; // All '\0' characters + + assertThrows(IllegalArgumentException.class, + () -> secretsRotationService.updatePassword(nulledPassword), + "Should throw IllegalArgumentException when password is all zeros"); + } + + @Test + void testUpdatePasswordCleansUpOnException() { + CryptoSecrets currentSecrets = mock(CryptoSecrets.class); + char[] newPassword = "newPassword123".toCharArray(); + + when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); + doThrow(new RuntimeException("Security service error")) + .when(appSecurityService).setSecrets(any()); + + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updatePassword(newPassword), + "Should throw SecretsRotationFailedException when setSecrets fails"); + + verify(currentSecrets).destroy(); + } + + @Test + void testUpdatePasswordThrowsSecretsRotationFailedWhenGetSecretsThrows() { + char[] newPassword = "newPassword123".toCharArray(); + + when(appSecurityService.getActualSecrets()) + .thenThrow(new RuntimeException("Failed to get secrets")); + + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updatePassword(newPassword), + "Should throw SecretsRotationFailedException" + + " when getActualSecrets fails"); + } + + @Test + void testUpdatePasswordNotDestroysCurrentSecretsIfItsNull() { + char[] newPassword = "newPassword123".toCharArray(); + char[] newPasswordCopy = newPassword.clone(); + + when(appSecurityService.getActualSecrets()).thenReturn(null); + + assertThrows(SecretsRotationFailedException.class, + () -> secretsRotationService.updatePassword(newPassword), + "Should throw SecretsRotationFailedException and still destroy newSecrets"); + + assertArrayEquals(newPasswordCopy, newPassword, + "New password should remain unchanged after exception"); + } } From ad338b30291920e6b04ef675672e3a25a101ec3e Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:44:48 +0200 Subject: [PATCH 21/30] Refactor AuthHandler to integrate SecretsRotationService for password updates --- .../activity/security/AuthActivity.java | 7 ++++- .../notesr/activity/security/AuthHandler.java | 27 ++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/app/notesr/activity/security/AuthActivity.java b/app/src/main/java/app/notesr/activity/security/AuthActivity.java index c778e757..596e03c9 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthActivity.java +++ b/app/src/main/java/app/notesr/activity/security/AuthActivity.java @@ -20,6 +20,7 @@ import app.notesr.activity.ActivityBase; import app.notesr.core.util.SecureStringBuilder; import app.notesr.service.security.AppSecurityService; +import app.notesr.service.security.rotation.SecretsRotationService; import lombok.AllArgsConstructor; import lombok.Getter; @@ -57,7 +58,11 @@ protected void onCreate(Bundle savedInstanceState) { String mode = getIntent().getStringExtra(EXTRA_MODE); var appSecurityService = new AppSecurityService(getApplicationContext()); - authHandler = new AuthHandler(this, appSecurityService, passwordBuilder); + var secretsRotationService = new SecretsRotationService(getApplicationContext(), + appSecurityService); + + authHandler = new AuthHandler(this, appSecurityService, secretsRotationService, + passwordBuilder); try { currentMode = Mode.valueOf(mode); 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 8a801f5a..97da88fe 100644 --- a/app/src/main/java/app/notesr/activity/security/AuthHandler.java +++ b/app/src/main/java/app/notesr/activity/security/AuthHandler.java @@ -34,6 +34,7 @@ import app.notesr.service.security.AppSecurityException; import app.notesr.service.security.AppSecurityService; import app.notesr.service.security.AuthenticationFailedException; +import app.notesr.service.security.rotation.SecretsRotationService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -43,6 +44,7 @@ public final class AuthHandler { private final AuthActivity activity; private final AppSecurityService appSecurityService; + private final SecretsRotationService secretsRotationService; private final SecureStringBuilder passwordBuilder; private int attempts = MAX_ATTEMPTS; @@ -119,21 +121,20 @@ public void recoverKey() { public void changePassword() { char[] password = proceedPasswordSetting(); - if (password != null) { - try { - Context context = activity.getApplicationContext(); - CryptoSecrets secrets = appSecurityService.getActualSecrets(); + if (password == null) { + // New password entered, but not confirmed (repeated by user) + return; + } - secrets.setPassword(password); - appSecurityService.setSecrets(secrets); - secrets.destroy(); + try { + secretsRotationService.updatePassword(password); - showToastMessage(R.string.updated); - activity.startActivity(new Intent(context, NotesListActivity.class)); - activity.finish(); - } catch (Exception e) { - throw new RuntimeException(e); - } + showToastMessage(R.string.updated); + activity.startActivity(new Intent(activity.getApplicationContext(), + NotesListActivity.class)); + activity.finish(); + } catch (Exception e) { + throw new RuntimeException(e); } } From 924c59ff1d9a408ace2a702ba9b659ed5527feb3 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sat, 1 Aug 2026 22:54:47 +0200 Subject: [PATCH 22/30] Rename SecretsRotationAndroidService to SecretsUpdateAndroidService --- app/src/main/AndroidManifest.xml | 2 +- .../java/app/notesr/activity/FsaResolver.java | 4 ++-- .../security/KeySetupCompletionHandler.java | 6 +++--- .../security/SecretsRotationActivity.java | 6 +++--- .../SecretsRotationBroadcastReceiver.java | 8 ++++---- .../service/security/AppSecurityService.java | 1 + .../SecretsRotationAndroidServiceStarter.java | 16 ++++++++-------- .../rotation/SecretsRotationService.java | 2 +- ...ice.java => SecretsUpdateAndroidService.java} | 12 ++++++------ ...SecretsRotationAndroidServiceStarterTest.java | 16 ++++++++-------- ...java => SecretsUpdateAndroidServiceTest.java} | 14 +++++++------- 11 files changed, 44 insertions(+), 43 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsRotationAndroidService.java => SecretsUpdateAndroidService.java} (93%) rename service/src/test/java/app/notesr/service/security/rotation/{SecretsRotationAndroidServiceTest.java => SecretsUpdateAndroidServiceTest.java} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fc407ee2..229a53e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -117,7 +117,7 @@ android:stopWithTask="false" android:foregroundServiceType="dataSync" /> diff --git a/app/src/main/java/app/notesr/activity/FsaResolver.java b/app/src/main/java/app/notesr/activity/FsaResolver.java index 5d9e809b..48359d3e 100644 --- a/app/src/main/java/app/notesr/activity/FsaResolver.java +++ b/app/src/main/java/app/notesr/activity/FsaResolver.java @@ -9,7 +9,7 @@ import app.notesr.activity.security.SecretsRotationActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.rotation.SecretsRotationAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; /** @@ -30,7 +30,7 @@ public final class FsaResolver { // new FsaEntry(AppMigrationAndroidService.class, MigrationActivity.class), // new FsaEntry(ExportAndroidService.class, ExportActivity.class), // new FsaEntry(ImportAndroidService.class, ImportActivity.class), - new FsaEntry(SecretsRotationAndroidService.class, SecretsRotationActivity.class) + new FsaEntry(SecretsUpdateAndroidService.class, SecretsRotationActivity.class) ); private final AndroidServiceRegistry servicesRegistry; diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index 97110cdf..81d9f5c0 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -25,7 +25,7 @@ import app.notesr.core.security.dto.CryptoSecrets; import app.notesr.service.security.AppSecurityService; import app.notesr.service.migration.DataVersionManager; -import app.notesr.service.security.rotation.SecretsRotationAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -88,8 +88,8 @@ private void onRegenerationConfirmed() { char[] password = getCurrentPassword(); byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8); - SecretCache.put(SecretsRotationAndroidService.NEW_KEY, keyBytes); - SecretCache.put(SecretsRotationAndroidService.PASSWORD, passwordBytes); + SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, keyBytes); + SecretCache.put(SecretsUpdateAndroidService.PASSWORD, passwordBytes); } catch (CharacterCodingException e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index 9c451c01..fd570d40 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -20,7 +20,7 @@ import app.notesr.activity.DialogFactory; import app.notesr.activity.note.list.NotesListActivity; import app.notesr.service.AndroidServiceRegistry; -import app.notesr.service.security.rotation.SecretsRotationAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import app.notesr.service.security.rotation.SecretsRotationAndroidServiceStarter; public final class SecretsRotationActivity extends ActivityBase { @@ -37,7 +37,7 @@ protected void onCreate(Bundle savedInstanceState) { this::onSecretsRotationFailed); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, - new IntentFilter(SecretsRotationAndroidService.BROADCAST_ACTION)); + new IntentFilter(SecretsUpdateAndroidService.BROADCAST_ACTION)); startSecretsRotationService(); } @@ -51,7 +51,7 @@ private void startSecretsRotationService() { AndroidServiceRegistry serviceRegistry = AndroidServiceRegistry .getInstance(getApplicationContext()); - if (!serviceRegistry.isServiceRunning(SecretsRotationAndroidService.class)) { + if (!serviceRegistry.isServiceRunning(SecretsUpdateAndroidService.class)) { try { new SecretsRotationAndroidServiceStarter().start(getApplicationContext()); } catch (CharacterCodingException e) { diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java index 2fb449f5..a95f9578 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java @@ -9,7 +9,7 @@ import android.content.Context; import android.content.Intent; -import app.notesr.service.security.rotation.SecretsRotationAndroidService; +import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @@ -19,11 +19,11 @@ public final class SecretsRotationBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (SecretsRotationAndroidService.BROADCAST_ACTION.equals(intent.getAction())) { - boolean isCompleted = intent.getBooleanExtra(SecretsRotationAndroidService.EXTRA_COMPLETE, + if (SecretsUpdateAndroidService.BROADCAST_ACTION.equals(intent.getAction())) { + boolean isCompleted = intent.getBooleanExtra(SecretsUpdateAndroidService.EXTRA_COMPLETE, false); - boolean isFailed = intent.getBooleanExtra(SecretsRotationAndroidService.EXTRA_FAIL, + boolean isFailed = intent.getBooleanExtra(SecretsUpdateAndroidService.EXTRA_FAIL, false); if (isCompleted) { diff --git a/service/src/main/java/app/notesr/service/security/AppSecurityService.java b/service/src/main/java/app/notesr/service/security/AppSecurityService.java index 049d0ee3..1e4d831f 100644 --- a/service/src/main/java/app/notesr/service/security/AppSecurityService.java +++ b/service/src/main/java/app/notesr/service/security/AppSecurityService.java @@ -17,6 +17,7 @@ import app.notesr.core.security.crypto.CryptoManager; import app.notesr.core.security.crypto.CryptoManagerProvider; import app.notesr.core.security.dto.CryptoSecrets; +import app.notesr.core.security.exception.SessionExpiredException; import app.notesr.core.util.CryptoSecretsValidator; import app.notesr.data.DatabaseProvider; import lombok.RequiredArgsConstructor; diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java index 7b0762b5..df3f1f22 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java @@ -7,7 +7,7 @@ import static java.util.Objects.requireNonNull; import static app.notesr.core.util.CharUtils.charsToBytes; -import static app.notesr.service.security.rotation.SecretsRotationAndroidService.EXTRA_CURRENT_STATE; +import static app.notesr.service.security.rotation.SecretsUpdateAndroidService.EXTRA_CURRENT_STATE; import android.content.Context; import android.content.Intent; @@ -38,16 +38,16 @@ public final class SecretsRotationAndroidServiceStarter implements AndroidServic @Override public void start(Context context) throws CharacterCodingException { if (payload == null) { - if (!SecretCache.contains(SecretsRotationAndroidService.NEW_KEY) - || !SecretCache.contains(SecretsRotationAndroidService.PASSWORD)) { + if (!SecretCache.contains(SecretsUpdateAndroidService.NEW_KEY) + || !SecretCache.contains(SecretsUpdateAndroidService.PASSWORD)) { throw new IllegalStateException("Secrets are not set"); } } else { - SecretCache.removeIfExists(SecretsRotationAndroidService.NEW_KEY); - SecretCache.removeIfExists(SecretsRotationAndroidService.PASSWORD); + SecretCache.removeIfExists(SecretsUpdateAndroidService.NEW_KEY); + SecretCache.removeIfExists(SecretsUpdateAndroidService.PASSWORD); - SecretCache.put(SecretsRotationAndroidService.NEW_KEY, payload.getNewKey()); - SecretCache.put(SecretsRotationAndroidService.PASSWORD, + SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, payload.getNewKey()); + SecretCache.put(SecretsUpdateAndroidService.PASSWORD, charsToBytes(payload.getNewPassword(), StandardCharsets.UTF_8)); } @@ -81,7 +81,7 @@ private Payload decryptPayload(ObjectMapper mapper, CryptoSecrets secrets, Strin } private Intent buildIntent(Context context) { - return new Intent(context, SecretsRotationAndroidService.class) + return new Intent(context, SecretsUpdateAndroidService.class) .putExtra(EXTRA_CURRENT_STATE, state); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 679fc9ae..cb3d6d7d 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -86,7 +86,7 @@ public void updatePassword(char[] newPassword) { * * @throws IllegalArgumentException If the new secrets are invalid. * @throws SecretsRotationFailedException If the secrets rotation fails. - * @see SecretsRotationAndroidService + * @see SecretsUpdateAndroidService * @see SecretsRotationAndroidServiceStarter */ public void updateSecrets( diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java similarity index 93% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index ffda9d57..f0063676 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -41,17 +41,17 @@ import lombok.Setter; @Setter(AccessLevel.PACKAGE) -public class SecretsRotationAndroidService extends AndroidService implements Runnable { +public class SecretsUpdateAndroidService extends AndroidService implements Runnable { - private static final String TAG = SecretsRotationAndroidService.class.getSimpleName(); + private static final String TAG = SecretsUpdateAndroidService.class.getSimpleName(); public static final String NEW_KEY = "new_key"; public static final String PASSWORD = "password"; - public static final String BROADCAST_ACTION = "secrets_rotation_service_broadcast"; + public static final String BROADCAST_ACTION = "secrets_update_service_broadcast"; public static final String EXTRA_CURRENT_STATE = "current_state"; - public static final String EXTRA_COMPLETE = "rotation_completed"; - public static final String EXTRA_FAIL = "rotation_failed"; - private static final String CHANNEL_ID = "secrets_rotation_service"; + public static final String EXTRA_COMPLETE = "update_completed"; + public static final String EXTRA_FAIL = "update_failed"; + private static final String CHANNEL_ID = "secrets_update_service"; private static final String CHANNEL_NAME = "Key Rotation"; private String dbName; diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java index 9d340520..50526e22 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java @@ -71,8 +71,8 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { byte[] newKey = "newKey".getBytes(); byte[] password = "password".getBytes(); - SecretCache.put(SecretsRotationAndroidService.NEW_KEY, newKey); - SecretCache.put(SecretsRotationAndroidService.PASSWORD, password); + SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, newKey); + SecretCache.put(SecretsUpdateAndroidService.PASSWORD, password); MockedConstruction mockedIntent = mockConstruction(Intent.class, (mock, context) -> @@ -85,7 +85,7 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { verify(context).startForegroundService(any(Intent.class)); Intent constructed = mockedIntent.constructed().get(0); verify(constructed) - .putExtra(eq(SecretsRotationAndroidService.EXTRA_CURRENT_STATE), + .putExtra(eq(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE), (Serializable) eq(null)); } } @@ -110,9 +110,9 @@ void testStartWithPayloadUpdatesCacheAndStartsService() throws Exception { starter.start(context); assertArrayEquals(expectedNewKey, - SecretCache.take(SecretsRotationAndroidService.NEW_KEY)); + SecretCache.take(SecretsUpdateAndroidService.NEW_KEY)); assertArrayEquals(expectedPasswordBytes, - SecretCache.take(SecretsRotationAndroidService.PASSWORD)); + SecretCache.take(SecretsUpdateAndroidService.PASSWORD)); verify(context).startForegroundService(any(Intent.class)); } @@ -153,13 +153,13 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { starter.start(context, secrets, encryptedPayload, stateJson); assertArrayEquals(expectedNewKey, - SecretCache.take(SecretsRotationAndroidService.NEW_KEY)); + SecretCache.take(SecretsUpdateAndroidService.NEW_KEY)); assertArrayEquals(expectedNewPasswordBytes, - SecretCache.take(SecretsRotationAndroidService.PASSWORD)); + SecretCache.take(SecretsUpdateAndroidService.PASSWORD)); verify(context).startForegroundService(any(Intent.class)); Intent constructed = mockedIntent.constructed().get(0); - verify(constructed).putExtra(SecretsRotationAndroidService.EXTRA_CURRENT_STATE, state); + verify(constructed).putExtra(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE, state); } } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java similarity index 93% rename from service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java index 3c34de52..bbe881db 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java @@ -43,7 +43,7 @@ import app.notesr.service.security.AppSecurityService; @ExtendWith(MockitoExtension.class) -class SecretsRotationAndroidServiceTest { +class SecretsUpdateAndroidServiceTest { @Mock private Context context; @@ -69,13 +69,13 @@ class SecretsRotationAndroidServiceTest { @Mock private AndroidServiceRegistry androidServiceRegistry; - private SecretsRotationAndroidService service; + private SecretsUpdateAndroidService service; private SecretsRotationState state; private CryptoSecrets newSecrets; @BeforeEach void setUp() { - service = spy(new SecretsRotationAndroidService()); + service = spy(new SecretsUpdateAndroidService()); state = new SecretsRotationState(); newSecrets = new CryptoSecrets(new byte[32], "password".toCharArray()); @@ -134,14 +134,14 @@ void testRunFailure() { void testOnCompleteCallsSendBroadcast() { doNothing().when(service).sendUpdateBroadcast(anyString()); service.onComplete(); - verify(service).sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_COMPLETE); + verify(service).sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_COMPLETE); } @Test void testOnFailCallsSendBroadcast() { doNothing().when(service).sendUpdateBroadcast(anyString()); service.onFail(); - verify(service).sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_FAIL); + verify(service).sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_FAIL); } @Test @@ -159,7 +159,7 @@ void testSendUpdateBroadcast() { doReturn(mock).when(mock).putExtra(anyString(), anyBoolean())); try (mockedIntent) { - service.sendUpdateBroadcast(SecretsRotationAndroidService.EXTRA_COMPLETE); + service.sendUpdateBroadcast(SecretsUpdateAndroidService.EXTRA_COMPLETE); verify(localBroadcastManager).sendBroadcast(any(Intent.class)); } @@ -196,7 +196,7 @@ void testOnStateUpdateUpdatesRegistry() { @Test void testOnStartCommand() { - when(intent.getSerializableExtra(SecretsRotationAndroidService.EXTRA_CURRENT_STATE)) + when(intent.getSerializableExtra(SecretsUpdateAndroidService.EXTRA_CURRENT_STATE)) .thenReturn(state); MockedStatic registryMock = mockStatic(AndroidServiceRegistry.class); From a4707cded239296e55e631563725a28b2e6306bb Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sat, 1 Aug 2026 23:36:29 +0200 Subject: [PATCH 23/30] Rename SecretsRotationAndroidServiceStarter to SecretsUpdateAndroidServiceStarter --- .../security/SecretsRotationActivity.java | 4 ++-- .../rotation/SecretsRotationService.java | 4 ++-- .../rotation/SecretsUpdateAndroidService.java | 8 +++---- ...> SecretsUpdateAndroidServiceStarter.java} | 2 +- ...cretsUpdateAndroidServiceStarterTest.java} | 24 +++++++++---------- 5 files changed, 21 insertions(+), 21 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsRotationAndroidServiceStarter.java => SecretsUpdateAndroidServiceStarter.java} (97%) rename service/src/test/java/app/notesr/service/security/rotation/{SecretsRotationAndroidServiceStarterTest.java => SecretsUpdateAndroidServiceStarterTest.java} (89%) diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java index fd570d40..c7104fbe 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java @@ -21,7 +21,7 @@ import app.notesr.activity.note.list.NotesListActivity; import app.notesr.service.AndroidServiceRegistry; import app.notesr.service.security.rotation.SecretsUpdateAndroidService; -import app.notesr.service.security.rotation.SecretsRotationAndroidServiceStarter; +import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; public final class SecretsRotationActivity extends ActivityBase { @@ -53,7 +53,7 @@ private void startSecretsRotationService() { if (!serviceRegistry.isServiceRunning(SecretsUpdateAndroidService.class)) { try { - new SecretsRotationAndroidServiceStarter().start(getApplicationContext()); + new SecretsUpdateAndroidServiceStarter().start(getApplicationContext()); } catch (CharacterCodingException e) { throw new RuntimeException(e); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index cb3d6d7d..84969ef5 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -72,7 +72,7 @@ public void updatePassword(char[] newPassword) { /** * Updates the crypto secrets (master key and password) and migrates all encrypted data. * This could be heavy and long-term operation, so it should be executed - * using {@link SecretsRotationAndroidServiceStarter}. + * using {@link SecretsUpdateAndroidServiceStarter}. *

* It performs a migration of the database and file blobs to the new encryption settings. * After the migration, the newSecrets are destroyed to minimize sensitive data @@ -87,7 +87,7 @@ public void updatePassword(char[] newPassword) { * @throws IllegalArgumentException If the new secrets are invalid. * @throws SecretsRotationFailedException If the secrets rotation fails. * @see SecretsUpdateAndroidService - * @see SecretsRotationAndroidServiceStarter + * @see SecretsUpdateAndroidServiceStarter */ public void updateSecrets( TransactionalFilesUtil txFiles, diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index f0063676..0c4d55f0 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -108,7 +108,7 @@ void showForegroundNotification(int startId) { @NonNull @Override protected AndroidServiceEntry getEntry(String payload, String state) { - return entryBuilder(SecretsRotationAndroidServiceStarter.class) + return entryBuilder(SecretsUpdateAndroidServiceStarter.class) .autoStart(true) .requiresAuth(true) .payload(payload) @@ -116,14 +116,14 @@ protected AndroidServiceEntry getEntry(String payload, String state) { .build(); } - private SecretsRotationAndroidServiceStarter.Payload getPayload() { - return new SecretsRotationAndroidServiceStarter.Payload( + private SecretsUpdateAndroidServiceStarter.Payload getPayload() { + return new SecretsUpdateAndroidServiceStarter.Payload( newSecrets.getKey(), newSecrets.getPassword() ); } - String encryptPayload(SecretsRotationAndroidServiceStarter.Payload payload) { + String encryptPayload(SecretsUpdateAndroidServiceStarter.Payload payload) { return getEncryptedJson(new ObjectMapper(), payload, appSecurityService.getActualSecrets()); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java similarity index 97% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java index df3f1f22..33484fd2 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java @@ -30,7 +30,7 @@ @NoArgsConstructor @AllArgsConstructor -public final class SecretsRotationAndroidServiceStarter implements AndroidServiceStarter { +public final class SecretsUpdateAndroidServiceStarter implements AndroidServiceStarter { private Payload payload; private SecretsRotationState state; diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java similarity index 89% rename from service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java rename to service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java index 50526e22..4425d419 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java @@ -39,14 +39,14 @@ import app.notesr.core.util.KeyUtils; @ExtendWith(MockitoExtension.class) -class SecretsRotationAndroidServiceStarterTest { +class SecretsUpdateAndroidServiceStarterTest { private static final int KEY_SIZE = 48; @Mock private Context context; - private SecretsRotationAndroidServiceStarter starter; + private SecretsUpdateAndroidServiceStarter starter; private ObjectMapper mapper; @BeforeEach @@ -62,7 +62,7 @@ void tearDown() { @Test void testStartNullPayloadNoCacheThrowsIllegalStateException() { - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); assertThrows(IllegalStateException.class, () -> starter.start(context)); } @@ -79,7 +79,7 @@ void testStartNullPayloadCachePresentStartsService() throws Exception { when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); starter.start(context); verify(context).startForegroundService(any(Intent.class)); @@ -99,14 +99,14 @@ void testStartWithPayloadUpdatesCacheAndStartsService() throws Exception { char[] newPassword = passwordStr.toCharArray(); byte[] expectedPasswordBytes = passwordStr.getBytes(StandardCharsets.UTF_8); - var payload = new SecretsRotationAndroidServiceStarter.Payload(newKey, newPassword); + var payload = new SecretsUpdateAndroidServiceStarter.Payload(newKey, newPassword); MockedConstruction mockedIntent = mockConstruction(Intent.class, (mock, context) -> when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsRotationAndroidServiceStarter(payload, null); + starter = new SecretsUpdateAndroidServiceStarter(payload, null); starter.start(context); assertArrayEquals(expectedNewKey, @@ -132,7 +132,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { char[] newPassword = newPasswordStr.toCharArray(); byte[] expectedNewPasswordBytes = newPasswordStr.getBytes(StandardCharsets.UTF_8); - var payload = new SecretsRotationAndroidServiceStarter.Payload(newKey, newPassword); + var payload = new SecretsUpdateAndroidServiceStarter.Payload(newKey, newPassword); String payloadJson = mapper.writeValueAsString(payload); @@ -149,7 +149,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { when(mock.putExtra(anyString(), (Serializable) any())).thenReturn(mock)); try (mockedIntent) { - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); starter.start(context, secrets, encryptedPayload, stateJson); assertArrayEquals(expectedNewKey, @@ -170,7 +170,7 @@ void testStartWithEncryptedDataInvalidSecretsThrowsDecryptionFailedException() { String encryptedPayload = "invalid_payload"; - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); assertThrows(DecryptionFailedException.class, () -> starter.start(context, secrets, encryptedPayload, null)); } @@ -185,14 +185,14 @@ void testStartWithEncryptedDataInvalidJsonThrowsJsonProcessingException() throws KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt("not a json"); - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); assertThrows(JsonProcessingException.class, () -> starter.start(context, secrets, encryptedPayload, null)); } @Test void testStartWithEncryptedDataNullSecretsThrowsNullPointerException() { - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); assertThrows(NullPointerException.class, () -> starter.start(context, null, "payload", null)); } @@ -201,7 +201,7 @@ void testStartWithEncryptedDataNullSecretsThrowsNullPointerException() { void testStartWithEncryptedDataNullPayloadThrowsNullPointerException() { byte[] key = new byte[KEY_SIZE]; CryptoSecrets secrets = new CryptoSecrets(key, "pass".toCharArray()); - starter = new SecretsRotationAndroidServiceStarter(); + starter = new SecretsUpdateAndroidServiceStarter(); assertThrows(NullPointerException.class, () -> starter.start(context, secrets, null, null)); } From ef9c57da4a9185667e328f7ea118edc0b788b8ea Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:14:48 +0200 Subject: [PATCH 24/30] Rename SecretsRotationState to SecretsUpdateState --- .../rotation/SecretsRotationStateHolder.java | 12 ++++++------ .../rotation/SecretsUpdateAndroidService.java | 8 ++++---- .../rotation/SecretsUpdateAndroidServiceStarter.java | 4 ++-- ...etsRotationState.java => SecretsUpdateState.java} | 10 +++++----- .../rotation/SecretsRotationServiceTest.java | 12 ++++++------ .../SecretsUpdateAndroidServiceStarterTest.java | 2 +- .../rotation/SecretsUpdateAndroidServiceTest.java | 4 ++-- 7 files changed, 26 insertions(+), 26 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsRotationState.java => SecretsUpdateState.java} (64%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java index 0abb54f8..b49352a6 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java @@ -12,20 +12,20 @@ @RequiredArgsConstructor public final class SecretsRotationStateHolder { - private final Consumer onUpdate; + private final Consumer onUpdate; - private SecretsRotationState state = new SecretsRotationState(); + private SecretsUpdateState state = new SecretsUpdateState(); - public SecretsRotationState getState() { - return SecretsRotationState.from(state); + public SecretsUpdateState getState() { + return SecretsUpdateState.from(state); } - public SecretsRotationStateHolder setState(SecretsRotationState newState) { + public SecretsRotationStateHolder setState(SecretsUpdateState newState) { if (newState == null) { return this; } - state = SecretsRotationState.from(newState); + state = SecretsUpdateState.from(newState); onUpdate.accept(state); return this; diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index 0c4d55f0..0b473b26 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -73,7 +73,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { newSecrets = getNewSecrets(); - var state = (SecretsRotationState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); + var state = (SecretsUpdateState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); stateHolder = new SecretsRotationStateHolder(this::onStateUpdate).setState(state); encryptedPayload = encryptPayload(getPayload()); @@ -127,7 +127,7 @@ String encryptPayload(SecretsUpdateAndroidServiceStarter.Payload payload) { return getEncryptedJson(new ObjectMapper(), payload, appSecurityService.getActualSecrets()); } - String serializeState(SecretsRotationState state) { + String serializeState(SecretsUpdateState state) { if (state == null) { return null; } @@ -165,7 +165,7 @@ void stopService() { stopSelf(); } - TransactionalFilesUtil getTransactionalFilesUtil(SecretsRotationState state) { + TransactionalFilesUtil getTransactionalFilesUtil(SecretsUpdateState state) { var filesUtils = new FilesUtils(); var transactionId = state.getTransactionId(); @@ -179,7 +179,7 @@ public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); } - void onStateUpdate(SecretsRotationState newState) { + void onStateUpdate(SecretsUpdateState newState) { AndroidServiceRegistry.getInstance(getApplicationContext()) .updateEntry(getEntry(encryptedPayload, serializeState(newState))); } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java index 33484fd2..ceb53956 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarter.java @@ -33,7 +33,7 @@ public final class SecretsUpdateAndroidServiceStarter implements AndroidServiceStarter { private Payload payload; - private SecretsRotationState state; + private SecretsUpdateState state; @Override public void start(Context context) throws CharacterCodingException { @@ -61,7 +61,7 @@ public void start(Context context, CryptoSecrets secrets, String payload, String var mapper = new ObjectMapper(); if (state != null) { - this.state = mapper.readValue(state, SecretsRotationState.class); + this.state = mapper.readValue(state, SecretsUpdateState.class); } this.payload = decryptPayload(mapper, secrets, payload); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java similarity index 64% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java index ae9ea6e4..d183547d 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationState.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java @@ -17,7 +17,7 @@ @NoArgsConstructor @ToString @EqualsAndHashCode -public final class SecretsRotationState implements Serializable { +public final class SecretsUpdateState implements Serializable { @Getter private SecretsRotationStatus status; @@ -25,21 +25,21 @@ public final class SecretsRotationState implements Serializable { @Getter private String transactionId; - public SecretsRotationState setStatus(SecretsRotationStatus status) { + public SecretsUpdateState setStatus(SecretsRotationStatus status) { this.status = status; return this; } - public SecretsRotationState setTransactionId(String transactionId) { + public SecretsUpdateState setTransactionId(String transactionId) { this.transactionId = transactionId; return this; } - public static SecretsRotationState from(SecretsRotationState state) { + public static SecretsUpdateState from(SecretsUpdateState state) { if (state == null) { return null; } - return new SecretsRotationState(state.getStatus(), state.getTransactionId()); + return new SecretsUpdateState(state.getStatus(), state.getTransactionId()); } } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index bed9b7a8..a537d7fb 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -68,7 +68,7 @@ class SecretsRotationServiceTest { private AppSecurityService appSecurityService; @Mock - private Consumer onUpdate; + private Consumer onUpdate; private SecretsRotationService secretsRotationService; private SecretsRotationStateHolder stateHolder; @@ -101,7 +101,7 @@ private CryptoSecrets createNewSecrets() { @Test void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.DONE)); + stateHolder.setState(new SecretsUpdateState().setStatus(SecretsRotationStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, @@ -116,7 +116,7 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { @Test void testUpdateSecretsAlreadyFailedThrowsException() { when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsRotationState().setStatus(SecretsRotationStatus.FAILED)); + stateHolder.setState(new SecretsUpdateState().setStatus(SecretsRotationStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); assertThrows(SecretsRotationFailedException.class, @@ -216,7 +216,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { byte[] expectedNewKey = newKey.clone(); stateHolder.setState( - new SecretsRotationState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); + new SecretsUpdateState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); @@ -411,12 +411,12 @@ void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { assertEquals(SecretsRotationStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), "Status should be updated in the state holder"); - verify(onUpdate).accept(any(SecretsRotationState.class)); + verify(onUpdate).accept(any(SecretsUpdateState.class)); } @Test void testGetStatusReturnsStatusFromStateHolder() { - stateHolder.setState(new SecretsRotationState() + stateHolder.setState(new SecretsUpdateState() .setStatus(SecretsRotationStatus.MOVING_BLOBS_DATA)); SecretsRotationStatus status = secretsRotationService.getStatus(stateHolder); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java index 4425d419..fa359a94 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java @@ -140,7 +140,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt(payloadJson); - SecretsRotationState state = new SecretsRotationState(SecretsRotationStatus.MOVING_DB_DATA, + SecretsUpdateState state = new SecretsUpdateState(SecretsRotationStatus.MOVING_DB_DATA, "tx123"); String stateJson = mapper.writeValueAsString(state); diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java index bbe881db..89ee7e0b 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java @@ -70,13 +70,13 @@ class SecretsUpdateAndroidServiceTest { private AndroidServiceRegistry androidServiceRegistry; private SecretsUpdateAndroidService service; - private SecretsRotationState state; + private SecretsUpdateState state; private CryptoSecrets newSecrets; @BeforeEach void setUp() { service = spy(new SecretsUpdateAndroidService()); - state = new SecretsRotationState(); + state = new SecretsUpdateState(); newSecrets = new CryptoSecrets(new byte[32], "password".toCharArray()); // Inject basic dependencies using setters From 6ce4bec40d2ce6dc4558d80285a44ad7d213fe38 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:16:28 +0200 Subject: [PATCH 25/30] Rename SecretsRotationStateHolder to SecretsUpdateStateHolder --- .../service/security/rotation/SecretsRotationService.java | 8 ++++---- .../security/rotation/SecretsUpdateAndroidService.java | 4 ++-- ...tionStateHolder.java => SecretsUpdateStateHolder.java} | 4 ++-- .../security/rotation/SecretsRotationServiceTest.java | 4 ++-- .../rotation/SecretsUpdateAndroidServiceTest.java | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsRotationStateHolder.java => SecretsUpdateStateHolder.java} (83%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 84969ef5..46fe9924 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -93,7 +93,7 @@ public void updateSecrets( TransactionalFilesUtil txFiles, DatabaseManager databaseManager, String dbName, - SecretsRotationStateHolder stateHolder, + SecretsUpdateStateHolder stateHolder, CryptoSecrets newSecrets) { try { @@ -181,7 +181,7 @@ public void updateSecrets( void migrateData( TransactionalFilesUtil txFiles, DatabaseManager databaseManager, - SecretsRotationStateHolder stateHolder, + SecretsUpdateStateHolder stateHolder, String dbName, byte[] currentKey, byte[] newKey, @@ -339,7 +339,7 @@ byte[] decryptBlobData(AesCryptor cryptor, byte[] data) * @param stateHolder The state holder. * @return The current {@link SecretsRotationStatus}. */ - SecretsRotationStatus getStatus(SecretsRotationStateHolder stateHolder) { + SecretsRotationStatus getStatus(SecretsUpdateStateHolder stateHolder) { return stateHolder.getState().getStatus(); } @@ -349,7 +349,7 @@ SecretsRotationStatus getStatus(SecretsRotationStateHolder stateHolder) { * @param stateHolder The state holder. * @param status The new status to set. */ - void setStatus(SecretsRotationStateHolder stateHolder, SecretsRotationStatus status) { + void setStatus(SecretsUpdateStateHolder stateHolder, SecretsRotationStatus status) { stateHolder.setState(stateHolder.getState().setStatus(status)); } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java index 0b473b26..b1cf4eca 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateAndroidService.java @@ -58,7 +58,7 @@ public class SecretsUpdateAndroidService extends AndroidService implements Runna private DatabaseManager databaseManager; private AppSecurityService appSecurityService; private SecretsRotationService secretsRotationService; - private SecretsRotationStateHolder stateHolder; + private SecretsUpdateStateHolder stateHolder; private CryptoSecrets newSecrets; private String encryptedPayload; @@ -74,7 +74,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { newSecrets = getNewSecrets(); var state = (SecretsUpdateState) intent.getSerializableExtra(EXTRA_CURRENT_STATE); - stateHolder = new SecretsRotationStateHolder(this::onStateUpdate).setState(state); + stateHolder = new SecretsUpdateStateHolder(this::onStateUpdate).setState(state); encryptedPayload = encryptPayload(getPayload()); var thread = new Thread(this); diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java similarity index 83% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java index b49352a6..d3c01eb0 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStateHolder.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStateHolder.java @@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public final class SecretsRotationStateHolder { +public final class SecretsUpdateStateHolder { private final Consumer onUpdate; @@ -20,7 +20,7 @@ public SecretsUpdateState getState() { return SecretsUpdateState.from(state); } - public SecretsRotationStateHolder setState(SecretsUpdateState newState) { + public SecretsUpdateStateHolder setState(SecretsUpdateState newState) { if (newState == null) { return this; } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index a537d7fb..ab4754bf 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -71,7 +71,7 @@ class SecretsRotationServiceTest { private Consumer onUpdate; private SecretsRotationService secretsRotationService; - private SecretsRotationStateHolder stateHolder; + private SecretsUpdateStateHolder stateHolder; private final String dbName = "test.db"; private final byte[] currentKey = new byte[KEY_SIZE]; @@ -81,7 +81,7 @@ class SecretsRotationServiceTest { @BeforeEach void setUp() { secretsRotationService = new SecretsRotationService(context, appSecurityService); - stateHolder = new SecretsRotationStateHolder(onUpdate); + stateHolder = new SecretsUpdateStateHolder(onUpdate); // Initialize keys for (int i = 0; i < KEY_SIZE; i++) { diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java index 89ee7e0b..bbba077f 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceTest.java @@ -89,7 +89,7 @@ void setUp() { @Test void testRunSuccess() { - service.setStateHolder(new SecretsRotationStateHolder(s -> {}) + service.setStateHolder(new SecretsUpdateStateHolder(s -> {}) .setState(state)); doReturn(txFiles).when(service).getTransactionalFilesUtil(any()); @@ -109,7 +109,7 @@ void testRunSuccess() { @Test void testRunFailure() { - service.setStateHolder(new SecretsRotationStateHolder(s -> {}) + service.setStateHolder(new SecretsUpdateStateHolder(s -> {}) .setState(state)); MockedStatic logMock = mockStatic(Log.class); From eccbd02d5ac2a28108c25f51e2e82a5315a0611c Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sun, 2 Aug 2026 00:17:59 +0200 Subject: [PATCH 26/30] Rename SecretsRotationStatus to SecretsUpdateStatus --- .../rotation/SecretsRotationService.java | 28 +++++++++---------- .../security/rotation/SecretsUpdateState.java | 4 +-- ...onStatus.java => SecretsUpdateStatus.java} | 6 ++-- .../rotation/SecretsRotationServiceTest.java | 24 ++++++++-------- ...ecretsUpdateAndroidServiceStarterTest.java | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) rename service/src/main/java/app/notesr/service/security/rotation/{SecretsRotationStatus.java => SecretsUpdateStatus.java} (77%) diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java index 46fe9924..bd73102c 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationService.java @@ -108,14 +108,14 @@ public void updateSecrets( currentSecrets = appSecurityService.getActualSecrets(); if (getStatus(stateHolder) == null) { - setStatus(stateHolder, SecretsRotationStatus.INITIALIZING); + setStatus(stateHolder, SecretsUpdateStatus.INITIALIZING); } - if (getStatus(stateHolder) == SecretsRotationStatus.DONE) { + if (getStatus(stateHolder) == SecretsUpdateStatus.DONE) { return; } - if (getStatus(stateHolder) == SecretsRotationStatus.FAILED) { + if (getStatus(stateHolder) == SecretsUpdateStatus.FAILED) { throw new SecretsRotationFailedException("Secrets rotation is already failed"); } @@ -140,18 +140,18 @@ public void updateSecrets( txFiles.commit(); } else { - if (getStatus(stateHolder).isBefore(SecretsRotationStatus.DONE)) { - setStatus(stateHolder, SecretsRotationStatus.DONE); + if (getStatus(stateHolder).isBefore(SecretsUpdateStatus.DONE)) { + setStatus(stateHolder, SecretsUpdateStatus.DONE); } } appSecurityService.setSecrets(CryptoSecrets.from(newSecrets)); - setStatus(stateHolder, SecretsRotationStatus.DONE); + setStatus(stateHolder, SecretsUpdateStatus.DONE); databaseManager.reinitProvider(newSecrets.getKey()); } catch (Exception e) { txFiles.rollback(); - setStatus(stateHolder, SecretsRotationStatus.FAILED); + setStatus(stateHolder, SecretsUpdateStatus.FAILED); throw new SecretsRotationFailedException("Secrets rotation failed", e); } finally { if (currentSecrets != null) { @@ -199,12 +199,12 @@ void migrateData( var currentDbFile = txFiles.getDatabaseFile(context, dbName); try { - if (getStatus(stateHolder).isBeforeOrEqual(SecretsRotationStatus.MOVING_BLOBS_DATA)) { - setStatus(stateHolder, SecretsRotationStatus.MOVING_BLOBS_DATA); + if (getStatus(stateHolder).isBeforeOrEqual(SecretsUpdateStatus.MOVING_BLOBS_DATA)) { + setStatus(stateHolder, SecretsUpdateStatus.MOVING_BLOBS_DATA); updateBlobsData(txFiles, currentDb, currentBlobsDir, currentCryptor, newCryptor); } - if (getStatus(stateHolder).isBeforeOrEqual(SecretsRotationStatus.MOVING_DB_DATA)) { + if (getStatus(stateHolder).isBeforeOrEqual(SecretsUpdateStatus.MOVING_DB_DATA)) { // Staging files for new database File stagedDbFile = txFiles.stageFile(currentDbFile); @@ -217,7 +217,7 @@ void migrateData( var tempDb = databaseManager.getDatabase(stagedDbFile.getAbsolutePath(), newKey); try { - setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); + setStatus(stateHolder, SecretsUpdateStatus.MOVING_DB_DATA); copyDbData(currentDb, tempDb); } finally { tempDb.close(); @@ -337,9 +337,9 @@ byte[] decryptBlobData(AesCryptor cryptor, byte[] data) * Retrieves the current status from the state holder. * * @param stateHolder The state holder. - * @return The current {@link SecretsRotationStatus}. + * @return The current {@link SecretsUpdateStatus}. */ - SecretsRotationStatus getStatus(SecretsUpdateStateHolder stateHolder) { + SecretsUpdateStatus getStatus(SecretsUpdateStateHolder stateHolder) { return stateHolder.getState().getStatus(); } @@ -349,7 +349,7 @@ SecretsRotationStatus getStatus(SecretsUpdateStateHolder stateHolder) { * @param stateHolder The state holder. * @param status The new status to set. */ - void setStatus(SecretsUpdateStateHolder stateHolder, SecretsRotationStatus status) { + void setStatus(SecretsUpdateStateHolder stateHolder, SecretsUpdateStatus status) { stateHolder.setState(stateHolder.getState().setStatus(status)); } } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java index d183547d..23de7e8f 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateState.java @@ -20,12 +20,12 @@ public final class SecretsUpdateState implements Serializable { @Getter - private SecretsRotationStatus status; + private SecretsUpdateStatus status; @Getter private String transactionId; - public SecretsUpdateState setStatus(SecretsRotationStatus status) { + public SecretsUpdateState setStatus(SecretsUpdateStatus status) { this.status = status; return this; } diff --git a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java similarity index 77% rename from service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java rename to service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java index 96da9003..7976fbbe 100644 --- a/service/src/main/java/app/notesr/service/security/rotation/SecretsRotationStatus.java +++ b/service/src/main/java/app/notesr/service/security/rotation/SecretsUpdateStatus.java @@ -10,7 +10,7 @@ @Getter @RequiredArgsConstructor -public enum SecretsRotationStatus { +public enum SecretsUpdateStatus { INITIALIZING("initializing", 0), MOVING_BLOBS_DATA("moving_blobs_data", 1), MOVING_DB_DATA("moving_db_data", 2), @@ -20,11 +20,11 @@ public enum SecretsRotationStatus { private final String status; private final int order; - public boolean isBefore(SecretsRotationStatus other) { + public boolean isBefore(SecretsUpdateStatus other) { return this.order < other.order; } - public boolean isBeforeOrEqual(SecretsRotationStatus other) { + public boolean isBeforeOrEqual(SecretsUpdateStatus other) { return this.order <= other.order; } } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java index ab4754bf..543abf3c 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsRotationServiceTest.java @@ -101,7 +101,7 @@ private CryptoSecrets createNewSecrets() { @Test void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsUpdateState().setStatus(SecretsRotationStatus.DONE)); + stateHolder.setState(new SecretsUpdateState().setStatus(SecretsUpdateStatus.DONE)); CryptoSecrets newSecrets = createNewSecrets(); secretsRotationService.updateSecrets(txFiles, databaseManager, dbName, @@ -109,14 +109,14 @@ void testUpdateSecretsAlreadyDoneReturnsImmediately() throws Exception { verify(databaseManager, never()).closeProvider(); verify(txFiles, never()).commit(); - assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), "Status should remain DONE if already DONE"); } @Test void testUpdateSecretsAlreadyFailedThrowsException() { when(appSecurityService.getActualSecrets()).thenReturn(createCurrentSecrets()); - stateHolder.setState(new SecretsUpdateState().setStatus(SecretsRotationStatus.FAILED)); + stateHolder.setState(new SecretsUpdateState().setStatus(SecretsUpdateStatus.FAILED)); CryptoSecrets newSecrets = createNewSecrets(); assertThrows(SecretsRotationFailedException.class, @@ -202,7 +202,7 @@ void testUpdateSecretsSuccessfulUpdateFromStart() throws Exception { "Copied secrets must preserve key bytes"); verify(databaseManager).reinitProvider(any()); - assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE after successful migration"); verify(currentSecrets).destroy(); @@ -216,7 +216,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { byte[] expectedNewKey = newKey.clone(); stateHolder.setState( - new SecretsUpdateState().setStatus(SecretsRotationStatus.MOVING_DB_DATA)); + new SecretsUpdateState().setStatus(SecretsUpdateStatus.MOVING_DB_DATA)); when(appSecurityService.getActualSecrets()).thenReturn(currentSecrets); when(txFiles.isCommitted()).thenReturn(true); @@ -237,7 +237,7 @@ void testUpdateSecretsAlreadyCommittedUpdatesStatusToDone() throws Exception { verify(databaseManager).reinitProvider(any()); - assertEquals(SecretsRotationStatus.DONE, stateHolder.getState().getStatus(), + assertEquals(SecretsUpdateStatus.DONE, stateHolder.getState().getStatus(), "Status should be DONE if transaction was already committed"); verify(currentSecrets).destroy(); @@ -266,7 +266,7 @@ void testUpdateSecretsMigrationFailureTriggersRollbackAndSetsFailed() { verify(currentSecrets).destroy(); verify(newSecrets).destroy(); - assertEquals(SecretsRotationStatus.FAILED, stateHolder.getState().getStatus(), + assertEquals(SecretsUpdateStatus.FAILED, stateHolder.getState().getStatus(), "Status should be FAILED after migration failure"); } @@ -407,9 +407,9 @@ void testDecryptBlobDataWrapsGeneralSecurityException() throws Exception { @Test void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { - secretsRotationService.setStatus(stateHolder, SecretsRotationStatus.MOVING_DB_DATA); + secretsRotationService.setStatus(stateHolder, SecretsUpdateStatus.MOVING_DB_DATA); - assertEquals(SecretsRotationStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), + assertEquals(SecretsUpdateStatus.MOVING_DB_DATA, stateHolder.getState().getStatus(), "Status should be updated in the state holder"); verify(onUpdate).accept(any(SecretsUpdateState.class)); } @@ -417,11 +417,11 @@ void testSetStatusUpdatesStateHolderAndTriggersOnUpdate() { @Test void testGetStatusReturnsStatusFromStateHolder() { stateHolder.setState(new SecretsUpdateState() - .setStatus(SecretsRotationStatus.MOVING_BLOBS_DATA)); + .setStatus(SecretsUpdateStatus.MOVING_BLOBS_DATA)); - SecretsRotationStatus status = secretsRotationService.getStatus(stateHolder); + SecretsUpdateStatus status = secretsRotationService.getStatus(stateHolder); - assertEquals(SecretsRotationStatus.MOVING_BLOBS_DATA, status, + assertEquals(SecretsUpdateStatus.MOVING_BLOBS_DATA, status, "Should return the correct status from the state holder"); } diff --git a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java index fa359a94..e9e691eb 100644 --- a/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java +++ b/service/src/test/java/app/notesr/service/security/rotation/SecretsUpdateAndroidServiceStarterTest.java @@ -140,7 +140,7 @@ void testStartWithEncryptedDataDecryptsAndStartsService() throws Exception { KeyUtils.getSecretKeyFromSecrets(secrets))); String encryptedPayload = encryptor.encrypt(payloadJson); - SecretsUpdateState state = new SecretsUpdateState(SecretsRotationStatus.MOVING_DB_DATA, + SecretsUpdateState state = new SecretsUpdateState(SecretsUpdateStatus.MOVING_DB_DATA, "tx123"); String stateJson = mapper.writeValueAsString(state); From 64efcb4d9ab978764b079c5e8f04a2e34a40a9c0 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:40:18 +0200 Subject: [PATCH 27/30] Document SessionExpiredException in getActualSecrets Javadoc --- .../java/app/notesr/service/security/AppSecurityService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/service/src/main/java/app/notesr/service/security/AppSecurityService.java b/service/src/main/java/app/notesr/service/security/AppSecurityService.java index 1e4d831f..37f22dad 100644 --- a/service/src/main/java/app/notesr/service/security/AppSecurityService.java +++ b/service/src/main/java/app/notesr/service/security/AppSecurityService.java @@ -80,6 +80,7 @@ public byte[] generateMasterKey() { * Retrieves the currently configured cryptographic secrets. * * @return the current {@link CryptoSecrets} if configured, or null if not yet initialized + * @throws SessionExpiredException if secrets are not configured or have expired. * @see #isAuthConfigured() */ public CryptoSecrets getActualSecrets() { From f5ce7ba1f3573b0507d276adad77d4e13654c69a Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:41:19 +0200 Subject: [PATCH 28/30] Add unit test for SessionExpiredException in AppSecurityServiceTest for getActualSecrets method --- .../service/security/AppSecurityServiceTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/service/src/test/java/app/notesr/service/security/AppSecurityServiceTest.java b/service/src/test/java/app/notesr/service/security/AppSecurityServiceTest.java index 4c555942..89c2670b 100644 --- a/service/src/test/java/app/notesr/service/security/AppSecurityServiceTest.java +++ b/service/src/test/java/app/notesr/service/security/AppSecurityServiceTest.java @@ -42,6 +42,7 @@ import app.notesr.core.security.crypto.CryptoManager; import app.notesr.core.security.crypto.CryptoManagerProvider; import app.notesr.core.security.dto.CryptoSecrets; +import app.notesr.core.security.exception.SessionExpiredException; import app.notesr.core.util.CryptoSecretsValidator; import app.notesr.data.DatabaseProvider; @@ -117,6 +118,15 @@ void testGetActualSecretsReturnsNull() { verify(mockCryptoManager).getSecrets(); } + @Test + void testGetActualSecretsThrowsSessionExpiredException() { + when(mockCryptoManager.getSecrets()) + .thenThrow(new SessionExpiredException("Session expired")); + + assertThrows(SessionExpiredException.class, () -> appSecurityService.getActualSecrets(), + "SessionExpiredException should be thrown"); + } + @Test void testIsAppBlockedReturnsTrue() { when(mockCryptoManager.isBlocked(mockContext)).thenReturn(true); From 447a0ec2e3d34a5139cc77b0c196271d7c59d702 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 11:45:25 +0200 Subject: [PATCH 29/30] Rename "rotate_key" string to "change_private_key" for clarity in menu and strings --- app/src/main/res/menu/menu_notes_list.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/menu/menu_notes_list.xml b/app/src/main/res/menu/menu_notes_list.xml index a583a923..bcb7525b 100644 --- a/app/src/main/res/menu/menu_notes_list.xml +++ b/app/src/main/res/menu/menu_notes_list.xml @@ -24,5 +24,5 @@ android:title="@string/change_access_code"/> + android:title="@string/change_private_key"/> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 628f07a1..a24b45bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,7 +48,7 @@ Change access code Create new access code Updated! - Rotate key + Change private key This action will cause all of your data to be re-encrypted.\nAre you sure? Re-encrypting data… Warning From 6e945d6ea9c748514e9833979ccfc8138d361301 Mon Sep 17 00:00:00 2001 From: zHd4 <38856321+zHd4@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:24:47 +0200 Subject: [PATCH 30/30] Rename SecretsRotationActivity to SecretsUpdateActivity and rename related classes and XMLs --- app/src/main/AndroidManifest.xml | 2 +- .../java/app/notesr/activity/FsaResolver.java | 4 ++-- .../security/KeySetupCompletionHandler.java | 6 +++--- ...tivity.java => SecretsUpdateActivity.java} | 20 +++++++++---------- ...va => SecretsUpdateBroadcastReceiver.java} | 10 +++++----- ...tation.xml => activity_secrets_update.xml} | 12 +++++------ ...d.xml => dialog_secrets_update_failed.xml} | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) rename app/src/main/java/app/notesr/activity/security/{SecretsRotationActivity.java => SecretsUpdateActivity.java} (80%) rename app/src/main/java/app/notesr/activity/security/{SecretsRotationBroadcastReceiver.java => SecretsUpdateBroadcastReceiver.java} (75%) rename app/src/main/res/layout/{activity_secrets_rotation.xml => activity_secrets_update.xml} (79%) rename app/src/main/res/layout/{dialog_secrets_rotation_failed.xml => dialog_secrets_update_failed.xml} (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 229a53e8..76b694ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,7 +51,7 @@ android:screenOrientation="locked" android:theme="@style/AppTheme" /> diff --git a/app/src/main/java/app/notesr/activity/FsaResolver.java b/app/src/main/java/app/notesr/activity/FsaResolver.java index 48359d3e..a34e72ed 100644 --- a/app/src/main/java/app/notesr/activity/FsaResolver.java +++ b/app/src/main/java/app/notesr/activity/FsaResolver.java @@ -7,7 +7,7 @@ import java.util.Set; -import app.notesr.activity.security.SecretsRotationActivity; +import app.notesr.activity.security.SecretsUpdateActivity; import app.notesr.service.AndroidServiceRegistry; import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import lombok.RequiredArgsConstructor; @@ -30,7 +30,7 @@ public final class FsaResolver { // new FsaEntry(AppMigrationAndroidService.class, MigrationActivity.class), // new FsaEntry(ExportAndroidService.class, ExportActivity.class), // new FsaEntry(ImportAndroidService.class, ImportActivity.class), - new FsaEntry(SecretsUpdateAndroidService.class, SecretsRotationActivity.class) + new FsaEntry(SecretsUpdateAndroidService.class, SecretsUpdateActivity.class) ); private final AndroidServiceRegistry servicesRegistry; diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java index 81d9f5c0..68587544 100644 --- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java +++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java @@ -94,10 +94,10 @@ private void onRegenerationConfirmed() { throw new RuntimeException(e); } - Intent secretsRotationIntent = new Intent(activity.getApplicationContext(), - SecretsRotationActivity.class); + Intent secretsUpdateIntent = new Intent(activity.getApplicationContext(), + SecretsUpdateActivity.class); - activity.startActivity(secretsRotationIntent); + activity.startActivity(secretsUpdateIntent); activity.finish(); } diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java b/app/src/main/java/app/notesr/activity/security/SecretsUpdateActivity.java similarity index 80% rename from app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java rename to app/src/main/java/app/notesr/activity/security/SecretsUpdateActivity.java index c7104fbe..8003847a 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationActivity.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsUpdateActivity.java @@ -23,23 +23,23 @@ import app.notesr.service.security.rotation.SecretsUpdateAndroidService; import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter; -public final class SecretsRotationActivity extends ActivityBase { +public final class SecretsUpdateActivity extends ActivityBase { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_secrets_rotation); + setContentView(R.layout.activity_secrets_update); applyInsets(findViewById(R.id.main)); disableBackButton(this); - SecretsRotationBroadcastReceiver broadcastReceiver = - new SecretsRotationBroadcastReceiver(this::onSecretsRotationComplete, - this::onSecretsRotationFailed); + SecretsUpdateBroadcastReceiver broadcastReceiver = + new SecretsUpdateBroadcastReceiver(this::onSecretsUpdateComplete, + this::onSecretsUpdateFailed); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(SecretsUpdateAndroidService.BROADCAST_ACTION)); - startSecretsRotationService(); + startSecretsUpdateService(); } @Override @@ -47,7 +47,7 @@ protected boolean requiresSession() { return false; } - private void startSecretsRotationService() { + private void startSecretsUpdateService() { AndroidServiceRegistry serviceRegistry = AndroidServiceRegistry .getInstance(getApplicationContext()); @@ -60,14 +60,14 @@ private void startSecretsRotationService() { } } - private void onSecretsRotationComplete() { + private void onSecretsUpdateComplete() { startActivity(new Intent(getApplicationContext(), NotesListActivity.class)); finish(); } - private void onSecretsRotationFailed() { + private void onSecretsUpdateFailed() { DialogFactory dialogFactory = new DialogFactory(this); - dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_secrets_rotation_failed) + dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_secrets_update_failed) .setTitle(R.string.error) .setCancelable(false) .setPositiveButton(R.string.ok, (dialog, which) -> { diff --git a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java b/app/src/main/java/app/notesr/activity/security/SecretsUpdateBroadcastReceiver.java similarity index 75% rename from app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java rename to app/src/main/java/app/notesr/activity/security/SecretsUpdateBroadcastReceiver.java index a95f9578..882a114c 100644 --- a/app/src/main/java/app/notesr/activity/security/SecretsRotationBroadcastReceiver.java +++ b/app/src/main/java/app/notesr/activity/security/SecretsUpdateBroadcastReceiver.java @@ -13,9 +13,9 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor -public final class SecretsRotationBroadcastReceiver extends BroadcastReceiver { - private final Runnable onSecretsRotationComplete; - private final Runnable onSecretsRotationFailed; +public final class SecretsUpdateBroadcastReceiver extends BroadcastReceiver { + private final Runnable onSecretsUpdateComplete; + private final Runnable onSecretsUpdateFailed; @Override public void onReceive(Context context, Intent intent) { @@ -27,9 +27,9 @@ public void onReceive(Context context, Intent intent) { false); if (isCompleted) { - onSecretsRotationComplete.run(); + onSecretsUpdateComplete.run(); } else if (isFailed) { - onSecretsRotationFailed.run(); + onSecretsUpdateFailed.run(); } } } diff --git a/app/src/main/res/layout/activity_secrets_rotation.xml b/app/src/main/res/layout/activity_secrets_update.xml similarity index 79% rename from app/src/main/res/layout/activity_secrets_rotation.xml rename to app/src/main/res/layout/activity_secrets_update.xml index 85e90aa6..003a4e7e 100644 --- a/app/src/main/res/layout/activity_secrets_rotation.xml +++ b/app/src/main/res/layout/activity_secrets_update.xml @@ -6,10 +6,10 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/activity_background" - tools:context=".activity.security.SecretsRotationActivity"> + tools:context=".activity.security.SecretsUpdateActivity"> + app:layout_constraintTop_toBottomOf="@+id/secretsUpdateTitleLabel" /> + app:layout_constraintTop_toBottomOf="@+id/secretsUpdateProgressBar" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_secrets_rotation_failed.xml b/app/src/main/res/layout/dialog_secrets_update_failed.xml similarity index 93% rename from app/src/main/res/layout/dialog_secrets_rotation_failed.xml rename to app/src/main/res/layout/dialog_secrets_update_failed.xml index 102bcd7e..5ed5a0e8 100644 --- a/app/src/main/res/layout/dialog_secrets_rotation_failed.xml +++ b/app/src/main/res/layout/dialog_secrets_update_failed.xml @@ -7,7 +7,7 @@ android:background="@color/dialog_background">