Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aacc9b4
Move secrets update logic from `crypto.update` to `rotation` package
zHd4 Jul 28, 2026
4d82791
Rename ReEncryptionActivity and associated resources to SecretsRotati…
zHd4 Jul 29, 2026
8746c8f
Rename re-encryption failed dialog layout to secrets rotation failed
zHd4 Jul 29, 2026
f9a59c2
Rename re-encryption warning dialog to secrets rotation warning
zHd4 Jul 29, 2026
1c7236f
Rename re-encryption failed error label to secrets rotation failed er…
zHd4 Jul 29, 2026
ab22d25
Rename ReEncryptionBroadcastReceiver to SecretsRotationBroadcastReceiver
zHd4 Jul 29, 2026
25ab8b4
Rename regenerate or import key to rotate key in menu and update asso…
zHd4 Jul 29, 2026
b31cbb2
Rename SecretsUpdateService to SecretsRotationService
zHd4 Jul 29, 2026
2d364f8
Rename SecretsUpdateAndroidService to SecretsRotationAndroidService
zHd4 Jul 29, 2026
704176e
Rename SecretsUpdateAndroidServiceStarter to SecretsRotationAndroidSe…
zHd4 Jul 29, 2026
2e9e917
Rename SecretsUpdateState to SecretsRotationState
zHd4 Jul 29, 2026
70c6bf4
Rename SecretsUpdateStateHolder to SecretsRotationStateHolder
zHd4 Jul 29, 2026
b62c75b
Rename SecretsUpdateStatus to SecretsRotationStatus
zHd4 Jul 29, 2026
9c64879
Rename SecretsUpdateFailedException to SecretsRotationFailedException
zHd4 Jul 29, 2026
a3495cf
Rename secrets update terminology to secrets rotation for consistency
zHd4 Jul 29, 2026
b30d9cd
Refactor secrets rotation classes to use AppSecurityService instead o…
zHd4 Jul 29, 2026
6bc2469
Fix SecretsRotationService to use copy of new secrets for `AppSecurit…
zHd4 Jul 30, 2026
0ff9f37
Verify CryptoSecrets are copied when updating secrets in SecretsRotat…
zHd4 Jul 30, 2026
ebf0f20
Add @EqualsAndHashCode to CryptoSecrets DTO
zHd4 Jul 31, 2026
8953c16
Refactor secret rotation service and add password update functionality
zHd4 Jul 31, 2026
ad338b3
Refactor AuthHandler to integrate SecretsRotationService for password…
zHd4 Jul 31, 2026
924c59f
Rename SecretsRotationAndroidService to SecretsUpdateAndroidService
zHd4 Aug 1, 2026
a4707cd
Rename SecretsRotationAndroidServiceStarter to SecretsUpdateAndroidSe…
zHd4 Aug 1, 2026
ef9c57d
Rename SecretsRotationState to SecretsUpdateState
zHd4 Aug 1, 2026
6ce4bec
Rename SecretsRotationStateHolder to SecretsUpdateStateHolder
zHd4 Aug 1, 2026
eccbd02
Rename SecretsRotationStatus to SecretsUpdateStatus
zHd4 Aug 1, 2026
64efcb4
Document SessionExpiredException in getActualSecrets Javadoc
zHd4 Aug 2, 2026
f5ce7ba
Add unit test for SessionExpiredException in AppSecurityServiceTest f…
zHd4 Aug 2, 2026
447a0ec
Rename "rotate_key" string to "change_private_key" for clarity in men…
zHd4 Aug 3, 2026
6e945d6
Rename SecretsRotationActivity to SecretsUpdateActivity and rename re…
zHd4 Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
android:screenOrientation="locked"
android:theme="@style/AppTheme" />
<activity
android:name=".activity.security.ReEncryptionActivity"
android:name=".activity.security.SecretsUpdateActivity"
android:exported="false"
android:screenOrientation="locked"
android:theme="@style/AppTheme.NoActionBar" />
Expand Down Expand Up @@ -117,7 +117,7 @@
android:stopWithTask="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".service.security.crypto.update.SecretsUpdateAndroidService"
android:name=".service.security.rotation.SecretsUpdateAndroidService"
android:exported="false"
android:stopWithTask="false"
android:foregroundServiceType="dataSync" />
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/app/notesr/activity/FsaResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import java.util.Set;

import app.notesr.activity.security.ReEncryptionActivity;
import app.notesr.activity.security.SecretsUpdateActivity;
import app.notesr.service.AndroidServiceRegistry;
import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService;
import app.notesr.service.security.rotation.SecretsUpdateAndroidService;
import lombok.RequiredArgsConstructor;

/**
Expand All @@ -30,7 +30,7 @@ public final class FsaResolver {
// new FsaEntry(AppMigrationAndroidService.class, MigrationActivity.class),
// new FsaEntry(ExportAndroidService.class, ExportActivity.class),
// new FsaEntry(ImportAndroidService.class, ImportActivity.class),
new FsaEntry(SecretsUpdateAndroidService.class, ReEncryptionActivity.class)
new FsaEntry(SecretsUpdateAndroidService.class, SecretsUpdateActivity.class)
);

private final AndroidServiceRegistry servicesRegistry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public boolean onCreateOptionsMenu(Menu menu) {

menuActions.put(R.id.lockAppButton, lockAction::lock);
menuActions.put(R.id.changePasswordMenuItem, this::startChangePasswordActivity);
menuActions.put(R.id.generateNewKeyMenuItem, generateNewKeyAction::startActivity);
menuActions.put(R.id.rotateKey, generateNewKeyAction::startActivity);
menuActions.put(R.id.exportMenuItem,
() -> startActivity(new Intent(this, ExportActivity.class)));
menuActions.put(R.id.importMenuItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
27 changes: 14 additions & 13 deletions app/src/main/java/app/notesr/activity/security/AuthHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import app.notesr.core.security.dto.CryptoSecrets;
import app.notesr.service.security.AppSecurityService;
import app.notesr.service.migration.DataVersionManager;
import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService;
import app.notesr.service.security.rotation.SecretsUpdateAndroidService;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
Expand Down Expand Up @@ -73,7 +73,7 @@ private void proceedFirstRun() {

private void proceedRegeneration() {
new DialogFactory(activity)
.getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning)
.getThemedAlertDialogBuilder(R.layout.dialog_secrets_rotation_warning)
.setTitle(R.string.warning)
.setPositiveButton(R.string.yes,
(dialog, which) -> onRegenerationConfirmed())
Expand All @@ -94,10 +94,10 @@ private void onRegenerationConfirmed() {
throw new RuntimeException(e);
}

Intent reEncryptionIntent = new Intent(activity.getApplicationContext(),
ReEncryptionActivity.class);
Intent secretsUpdateIntent = new Intent(activity.getApplicationContext(),
SecretsUpdateActivity.class);

activity.startActivity(reEncryptionIntent);
activity.startActivity(secretsUpdateIntent);
activity.finish();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,34 @@
import app.notesr.activity.DialogFactory;
import app.notesr.activity.note.list.NotesListActivity;
import app.notesr.service.AndroidServiceRegistry;
import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService;
import app.notesr.service.security.crypto.update.SecretsUpdateAndroidServiceStarter;
import app.notesr.service.security.rotation.SecretsUpdateAndroidService;
import app.notesr.service.security.rotation.SecretsUpdateAndroidServiceStarter;

public final class ReEncryptionActivity extends ActivityBase {
public final class SecretsUpdateActivity extends ActivityBase {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_re_encryption);
setContentView(R.layout.activity_secrets_update);
applyInsets(findViewById(R.id.main));
disableBackButton(this);

ReEncryptionBroadcastReceiver broadcastReceiver =
new ReEncryptionBroadcastReceiver(this::onReEncryptionComplete,
this::onReEncryptionFailed);
SecretsUpdateBroadcastReceiver broadcastReceiver =
new SecretsUpdateBroadcastReceiver(this::onSecretsUpdateComplete,
this::onSecretsUpdateFailed);

LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver,
new IntentFilter(SecretsUpdateAndroidService.BROADCAST_ACTION));

startReEncryptionService();
startSecretsUpdateService();
}

@Override
protected boolean requiresSession() {
return false;
}

private void startReEncryptionService() {
private void startSecretsUpdateService() {
AndroidServiceRegistry serviceRegistry = AndroidServiceRegistry
.getInstance(getApplicationContext());

Expand All @@ -60,14 +60,14 @@ private void startReEncryptionService() {
}
}

private void onReEncryptionComplete() {
private void onSecretsUpdateComplete() {
startActivity(new Intent(getApplicationContext(), NotesListActivity.class));
finish();
}

private void onReEncryptionFailed() {
private void onSecretsUpdateFailed() {
DialogFactory dialogFactory = new DialogFactory(this);
dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_failed)
dialogFactory.getThemedAlertDialogBuilder(R.layout.dialog_secrets_update_failed)
.setTitle(R.string.error)
.setCancelable(false)
.setPositiveButton(R.string.ok, (dialog, which) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import android.content.Context;
import android.content.Intent;

import app.notesr.service.security.crypto.update.SecretsUpdateAndroidService;
import app.notesr.service.security.rotation.SecretsUpdateAndroidService;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public final class ReEncryptionBroadcastReceiver extends BroadcastReceiver {
private final Runnable onReEncryptionComplete;
private final Runnable onReEncryptionFailed;
public final class SecretsUpdateBroadcastReceiver extends BroadcastReceiver {
private final Runnable onSecretsUpdateComplete;
private final Runnable onSecretsUpdateFailed;

@Override
public void onReceive(Context context, Intent intent) {
Expand All @@ -27,9 +27,9 @@ public void onReceive(Context context, Intent intent) {
false);

if (isCompleted) {
onReEncryptionComplete.run();
onSecretsUpdateComplete.run();
} else if (isFailed) {
onReEncryptionFailed.run();
onSecretsUpdateFailed.run();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/activity_background"
tools:context=".activity.security.ReEncryptionActivity">
tools:context=".activity.security.SecretsUpdateActivity">

<TextView
android:id="@+id/reEncryptionNameLabel"
android:id="@+id/secretsUpdateTitleLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="224dp"
Expand All @@ -20,24 +20,24 @@
app:layout_constraintTop_toTopOf="parent" />

<ProgressBar
android:id="@+id/reEncryptionProgressBar"
android:id="@+id/secretsUpdateProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/reEncryptionNameLabel" />
app:layout_constraintTop_toBottomOf="@+id/secretsUpdateTitleLabel" />

<TextView
android:id="@+id/reEncryptionWarningLabel"
android:id="@+id/secretsUpdateWarningLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/this_may_take_a_long_time"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/reEncryptionProgressBar" />
app:layout_constraintTop_toBottomOf="@+id/secretsUpdateProgressBar" />
</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
android:background="@color/dialog_background">

<TextView
android:id="@+id/reEncryptionFailedErrorText"
android:id="@+id/secretsUpdateFailedErrorLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/menu/menu_notes_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
<item android:id="@+id/changePasswordMenuItem"
android:title="@string/change_access_code"/>

<item android:id="@+id/generateNewKeyMenuItem"
android:title="@string/regenerate_or_import_key"/>
<item android:id="@+id/rotateKey"
android:title="@string/change_private_key"/>
</menu>
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<string name="change_access_code">Change access code</string>
<string name="create_new_access_code">Create new access code</string>
<string name="updated">Updated!</string>
<string name="regenerate_or_import_key">Regenerate or import key</string>
<string name="change_private_key">Change private key</string>
<string name="key_regeneration_warning">This action will cause all of your data to be re-encrypted.\nAre you sure?</string>
<string name="re_encrypting_data">Re-encrypting data…</string>
<string name="warning">Warning</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -20,6 +19,7 @@
*/
@AllArgsConstructor
@Data
@EqualsAndHashCode
public final class CryptoSecrets {

public static final int MASTER_KEY_SIZE = 48;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,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() {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
* SPDX-License-Identifier: MIT
*/

package app.notesr.service.security.crypto.update;
package app.notesr.service.security.rotation;

import app.notesr.data.AppDatabase;

/**
* Interface for managing database instances and the global database provider state
* during secrets updates.
* during secrets rotation.
*/
public interface DatabaseManager {
AppDatabase getDatabase(String name, byte[] key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: MIT
*/

package app.notesr.service.security.crypto.update;
package app.notesr.service.security.rotation;

import android.content.Context;
import android.util.Log;
Expand Down
Loading
Loading