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">