From ba3667ab51be47a6aa77dbc3b875933121fa4bb4 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Wed, 11 Dec 2024 16:30:56 +0100 Subject: [PATCH 001/117] restore config by recoverykey auto restore when bkup exists added new VaultStates --- .../org/cryptomator/common/vaults/Vault.java | 20 +++++ .../common/vaults/VaultListManager.java | 58 ++++++++++++- .../cryptomator/common/vaults/VaultState.java | 4 + .../ui/mainwindow/MainWindowModule.java | 3 +- .../ui/mainwindow/VaultDetailController.java | 2 + .../VaultDetailMissingVaultController.java | 15 +++- .../mainwindow/VaultListCellController.java | 2 + .../ui/recoverykey/RecoveryKeyComponent.java | 7 ++ .../RecoveryKeyResetPasswordController.java | 87 ++++++++++++++++--- src/main/resources/fxml/vault_detail.fxml | 2 + .../fxml/vault_detail_missing_masterkey.fxml | 44 ++++++++++ .../vault_detail_missing_vault_config.fxml | 44 ++++++++++ src/main/resources/i18n/strings.properties | 7 ++ 13 files changed, 277 insertions(+), 18 deletions(-) create mode 100644 src/main/resources/fxml/vault_detail_missing_masterkey.fxml create mode 100644 src/main/resources/fxml/vault_detail_missing_vault_config.fxml diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index e65734e68..ca4e86090 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -70,6 +70,8 @@ public class Vault { private final BooleanBinding missing; private final BooleanBinding needsMigration; private final BooleanBinding unknownError; + private final BooleanBinding missingMasterkey; + private final BooleanBinding missingVaultConfig; private final ObjectBinding mountPoint; private final Mounter mounter; private final Settings settings; @@ -96,6 +98,8 @@ public class Vault { this.processing = Bindings.createBooleanBinding(this::isProcessing, state); this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state); this.missing = Bindings.createBooleanBinding(this::isMissing, state); + this.missingMasterkey = Bindings.createBooleanBinding(this::isMissingMasterkey, state); + this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state); this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state); this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state); this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state); @@ -315,6 +319,22 @@ public class Vault { return state.get() == VaultState.Value.ERROR; } + public BooleanBinding missingMasterkeyProperty() { + return missingMasterkey; + } + + public boolean isMissingMasterkey() { + return state.get() == VaultState.Value.MASTERKEY_MISSING; + } + + public BooleanBinding missingVaultConfigProperty() { + return missingVaultConfig; + } + + public boolean isMissingVaultConfig() { + return state.get() == VaultState.Value.VAULT_CONFIG_MISSING; + } + public ReadOnlyStringProperty displayNameProperty() { return vaultSettings.displayName; } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 87faff77a..c3b54a836 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -25,15 +25,20 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.ResourceBundle; +import java.util.stream.Stream; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; import static org.cryptomator.common.vaults.VaultState.Value.ERROR; import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; +import static org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; @Singleton public class VaultListManager { @@ -129,7 +134,7 @@ public class VaultListManager { VaultState state = vault.stateProperty(); VaultState.Value previousState = state.getValue(); return switch (previousState) { - case LOCKED, NEEDS_MIGRATION, MISSING -> { + case LOCKED, NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING -> { try { var determinedState = determineVaultState(vault.getPath()); if (determinedState == LOCKED) { @@ -149,7 +154,56 @@ public class VaultListManager { } private static VaultState.Value determineVaultState(Path pathToVault) throws IOException { - if (!Files.exists(pathToVault)) { + Path pathToVaultConfig = Path.of(pathToVault.toString(),"vault.cryptomator"); + Path pathToMasterkey = Path.of(pathToVault.toString(),"masterkey.cryptomator"); + if (!Files.exists(pathToVaultConfig)) { + try (Stream files = Files.list(pathToVaultConfig.getParent())) { + Path backupFile = files.filter(file -> { + String fileName = file.getFileName().toString(); + return fileName.startsWith("vault.cryptomator") && fileName.endsWith(".bkup"); + }).findFirst().orElse(null); + + if (backupFile != null) { + try { + Files.copy(backupFile, pathToVaultConfig, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOG.error("error",e); + return VAULT_CONFIG_MISSING; + } + } else { + return VAULT_CONFIG_MISSING; + } + } catch (IOException e) { + LOG.error("error",e); + return VAULT_CONFIG_MISSING; + } + + } + else if (!Files.exists(pathToMasterkey)) { + //return VaultState.Value.MASTERKEY_MISSING; + try (Stream files = Files.list(pathToMasterkey.getParent())) { + Path backupFile = files.filter(file -> { + String fileName = file.getFileName().toString(); + return fileName.startsWith("masterkey.cryptomator") && fileName.endsWith(".bkup"); + }).findFirst().orElse(null); + + if (backupFile != null) { + try { + Files.copy(backupFile, pathToMasterkey, StandardCopyOption.REPLACE_EXISTING); + return MASTERKEY_MISSING; + } catch (IOException e) { + LOG.error("error",e); + return MASTERKEY_MISSING; + } + } else { + return MASTERKEY_MISSING; + } + } catch (IOException e) { + LOG.error("error",e); + return MASTERKEY_MISSING; + } + } + else if (!Files.exists(pathToVault)) { return VaultState.Value.MISSING; } return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) { diff --git a/src/main/java/org/cryptomator/common/vaults/VaultState.java b/src/main/java/org/cryptomator/common/vaults/VaultState.java index ff09c8b82..bfe5a3713 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultState.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultState.java @@ -25,6 +25,10 @@ public class VaultState extends ObservableValueBase implements */ MISSING, + VAULT_CONFIG_MISSING, + + MASTERKEY_MISSING, + /** * Vault requires migration to a newer vault format */ diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index 5fdaa6a8a..b563cf69c 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -16,6 +16,7 @@ import org.cryptomator.ui.common.StageInitializer; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.migration.MigrationComponent; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent; import org.cryptomator.ui.stats.VaultStatisticsComponent; import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent; @@ -30,7 +31,7 @@ import javafx.stage.Stage; import java.util.Map; import java.util.ResourceBundle; -@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class}) +@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class}) abstract class MainWindowModule { @Provides diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index 7e309fdaf..4c889adfc 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -53,6 +53,8 @@ public class VaultDetailController implements FxController { case PROCESSING -> FontAwesome5Icon.SPINNER; case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN; case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; + case VAULT_CONFIG_MISSING -> FontAwesome5Icon.COGS; + case MASTERKEY_MISSING -> FontAwesome5Icon.KEY; }; } else { return FontAwesome5Icon.EXCLAMATION_TRIANGLE; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index 372d29040..058d53e23 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -3,6 +3,7 @@ package org.cryptomator.ui.mainwindow; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.cryptomator.ui.removevault.RemoveVaultComponent; import javax.inject.Inject; @@ -22,14 +23,17 @@ public class VaultDetailMissingVaultController implements FxController { private final RemoveVaultComponent.Builder removeVault; private final ResourceBundle resourceBundle; private final Stage window; + private final RecoveryKeyComponent.Factory recoveryKeyWindow; + @Inject - public VaultDetailMissingVaultController(ObjectProperty vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window) { + public VaultDetailMissingVaultController(ObjectProperty vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window, RecoveryKeyComponent.Factory recoveryKeyWindow) { this.vault = vault; this.removeVault = removeVault; this.resourceBundle = resourceBundle; this.window = window; + this.recoveryKeyWindow = recoveryKeyWindow; } @FXML @@ -42,6 +46,15 @@ public class VaultDetailMissingVaultController implements FxController { removeVault.vault(vault.get()).build().showRemoveVault(); } + @FXML + void restoreVaultConfig(){ + recoveryKeyWindow.create(vault.get(), window).showRecoveryKeyRecoverWindow("Recover VaultConfig"); + } + @FXML + void restoreMasterkey(){ + recoveryKeyWindow.create(vault.get(), window).showRecoveryKeyRecoverWindow("Recover Masterkey"); + } + @FXML void changeLocation() { // copied from ChooseExistingVaultController class diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java index 38d7ed1c7..884171c8e 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java @@ -47,6 +47,8 @@ public class VaultListCellController implements FxController { case PROCESSING -> FontAwesome5Icon.SPINNER; case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN; case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; + case VAULT_CONFIG_MISSING -> FontAwesome5Icon.COGS; + case MASTERKEY_MISSING -> FontAwesome5Icon.KEY; }; } else { return FontAwesome5Icon.EXCLAMATION_TRIANGLE; diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index 3986fa01d..c546a8806 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -38,6 +38,13 @@ public interface RecoveryKeyComponent { stage.show(); } + default void showRecoveryKeyRecoverWindow(String title) { + Stage stage = window(); + stage.setScene(recoverScene().get()); + stage.setTitle(title); + stage.sizeToScene(); + stage.show(); + } @Subcomponent.Factory interface Factory { diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 18a952ea5..b3299ca66 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -2,6 +2,13 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.cryptolib.api.CryptorProvider; +import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -18,8 +25,17 @@ import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Comparator; import java.util.concurrent.ExecutorService; +import static org.cryptomator.common.Constants.DEFAULT_KEY_ID; +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; + @RecoveryKeyScoped public class RecoveryKeyResetPasswordController implements FxController { @@ -32,11 +48,12 @@ public class RecoveryKeyResetPasswordController implements FxController { private final StringProperty recoveryKey; private final Lazy recoverResetPasswordSuccessScene; private final FxApplicationWindows appWindows; + private final MasterkeyFileAccess masterkeyFileAccess; public NewPasswordController newPasswordController; @Inject - public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) { + public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, FxApplicationWindows appWindows, MasterkeyFileAccess masterkeyFileAccess) { this.window = window; this.vault = vault; this.recoveryKeyFactory = recoveryKeyFactory; @@ -44,6 +61,7 @@ public class RecoveryKeyResetPasswordController implements FxController { this.recoveryKey = recoveryKey; this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene; this.appWindows = appWindows; + this.masterkeyFileAccess = masterkeyFileAccess; } @FXML @@ -53,19 +71,60 @@ public class RecoveryKeyResetPasswordController implements FxController { @FXML public void resetPassword() { - Task task = new ResetPasswordTask(); - task.setOnScheduled(event -> { - LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath()); - }); - task.setOnSucceeded(event -> { - LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath()); - window.setScene(recoverResetPasswordSuccessScene.get()); - }); - task.setOnFailed(event -> { - LOG.error("Resetting password failed.", task.getException()); - appWindows.showErrorWindow(task.getException(), window, null); - }); - executor.submit(task); + if(vault.isMissingVaultConfig()){ + Path vaultPath = vault.getPath(); + Path recoveryPath = vaultPath.resolve("r"); + try { + Files.createDirectory(recoveryPath); + recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters()); + } catch (IOException e) { + LOG.error("Creating directory or recovering masterkey failed", e); + } + + Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME); + try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFilePath, newPasswordController.passwordField.getCharacters())) { + try { + MasterkeyLoader loader = ignored -> masterkey.copy(); + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // + .withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC) // + .withKeyLoader(loader) // + .withShorteningThreshold(220) // + .build(); + CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID); + } catch (CryptoException | IOException e) { + LOG.error("Recovering vault failed", e); + } + Files.move(masterkeyFilePath, vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING); + Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME)); + try (var paths = Files.walk(recoveryPath)) { + paths.sorted(Comparator.reverseOrder()).forEach(p -> { + try { + Files.delete(p); + } catch (IOException e) { + LOG.info("Unable to delete {}. Please delete it manually.", p); + } + }); + } + window.setScene(recoverResetPasswordSuccessScene.get()); + } catch (IOException e) { + LOG.error("Moving recovered files failed", e); + } + } + else { + Task task = new ResetPasswordTask(); + task.setOnScheduled(event -> { + LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath()); + }); + task.setOnSucceeded(event -> { + LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath()); + window.setScene(recoverResetPasswordSuccessScene.get()); + }); + task.setOnFailed(event -> { + LOG.error("Resetting password failed.", task.getException()); + appWindows.showErrorWindow(task.getException(), window, null); + }); + executor.submit(task); + } } private class ResetPasswordTask extends Task { diff --git a/src/main/resources/fxml/vault_detail.fxml b/src/main/resources/fxml/vault_detail.fxml index 8ca11a34f..b87df488a 100644 --- a/src/main/resources/fxml/vault_detail.fxml +++ b/src/main/resources/fxml/vault_detail.fxml @@ -53,5 +53,7 @@ + + diff --git a/src/main/resources/fxml/vault_detail_missing_masterkey.fxml b/src/main/resources/fxml/vault_detail_missing_masterkey.fxml new file mode 100644 index 000000000..87d1d9b9e --- /dev/null +++ b/src/main/resources/fxml/vault_detail_missing_masterkey.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/vault_detail_missing_vault_config.fxml b/src/main/resources/fxml/vault_detail_missing_vault_config.fxml new file mode 100644 index 000000000..8291b5de6 --- /dev/null +++ b/src/main/resources/fxml/vault_detail_missing_vault_config.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 1df6bc244..faa7eef41 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -429,6 +429,13 @@ main.vaultDetail.missing.info=Cryptomator could not find a vault at this path. main.vaultDetail.missing.recheck=Recheck main.vaultDetail.missing.remove=Remove from Vault List… main.vaultDetail.missing.changeLocation=Change Vault Location… +### Missing Vault Config +main.vaultDetail.missingVaultConfig.info=VaultConfig is missing. +main.vaultDetail.missingVaultConfig.restore=Restore VaultConfig +### Missing Masterkey +main.vaultDetail.missingMasterkey.info=Masterkey is missing. +main.vaultDetail.missingMasterkey.restore=Restore Masterkey + ### Needs Migration main.vaultDetail.migrateButton=Upgrade Vault main.vaultDetail.migratePrompt=Your vault needs to be upgraded to a new format, before you can access it From aa95ad40c06955fd5a529dfd2dd931905c58dff3 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Mon, 13 Jan 2025 15:31:55 +0100 Subject: [PATCH 002/117] added vault config success scene --- .../org/cryptomator/ui/common/FxmlFile.java | 1 + .../ui/recoverykey/RecoveryKeyModule.java | 7 +++ .../RecoveryKeyResetPasswordController.java | 14 ++++- ...ecoverykey_reset_vault_config_success.fxml | 53 +++++++++++++++++++ src/main/resources/i18n/strings.properties | 2 + 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/fxml/recoverykey_reset_vault_config_success.fxml diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 6c5068684..13c0d4609 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -44,6 +44,7 @@ public enum FxmlFile { RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), // RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), // RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), // + RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS("/fxml/recoverykey_reset_vault_config_success.fxml"), // RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), // REMOVE_CERT("/fxml/remove_cert.fxml"), // REMOVE_VAULT("/fxml/remove_vault.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index 06095eebc..03b1bc029 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -105,6 +105,13 @@ abstract class RecoveryKeyModule { return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS); } + @Provides + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS) + @RecoveryKeyScoped + static Scene provideRecoveryKeyResetVaultConfigSuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS); + } + // ------------------ diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index b3299ca66..c5b466430 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -47,19 +47,29 @@ public class RecoveryKeyResetPasswordController implements FxController { private final ExecutorService executor; private final StringProperty recoveryKey; private final Lazy recoverResetPasswordSuccessScene; + private final Lazy recoverResetVaultConfigSuccessScene; private final FxApplicationWindows appWindows; private final MasterkeyFileAccess masterkeyFileAccess; public NewPasswordController newPasswordController; @Inject - public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, FxApplicationWindows appWindows, MasterkeyFileAccess masterkeyFileAccess) { + public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, // + @RecoveryKeyWindow Vault vault, // + RecoveryKeyFactory recoveryKeyFactory, // + ExecutorService executor, // + @RecoveryKeyWindow StringProperty recoveryKey, // + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS) Lazy recoverResetVaultConfigSuccessScene, // + FxApplicationWindows appWindows, // + MasterkeyFileAccess masterkeyFileAccess) { this.window = window; this.vault = vault; this.recoveryKeyFactory = recoveryKeyFactory; this.executor = executor; this.recoveryKey = recoveryKey; this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene; + this.recoverResetVaultConfigSuccessScene = recoverResetVaultConfigSuccessScene; this.appWindows = appWindows; this.masterkeyFileAccess = masterkeyFileAccess; } @@ -105,7 +115,7 @@ public class RecoveryKeyResetPasswordController implements FxController { } }); } - window.setScene(recoverResetPasswordSuccessScene.get()); + window.setScene(recoverResetVaultConfigSuccessScene.get()); } catch (IOException e) { LOG.error("Moving recovered files failed", e); } diff --git a/src/main/resources/fxml/recoverykey_reset_vault_config_success.fxml b/src/main/resources/fxml/recoverykey_reset_vault_config_success.fxml new file mode 100644 index 000000000..196617483 --- /dev/null +++ b/src/main/resources/fxml/recoverykey_reset_vault_config_success.fxml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 008a6c945..6b4420ed5 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -581,7 +581,6 @@ passwordStrength.messageLabel.1=Weak passwordStrength.messageLabel.2=Fair passwordStrength.messageLabel.3=Strong passwordStrength.messageLabel.4=Very strong -password.promptText=Enter password # Quit quit.title=Quit Application From f5ecf846f21bdac8161ae11cb8ec11bb132bf74a Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Sat, 5 Jul 2025 14:15:59 +0200 Subject: [PATCH 051/117] resolve SonarCloud issue by safely accessing Optional value --- .../org/cryptomator/ui/mainwindow/VaultListController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index fa3d96d0e..3d63fd6be 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -254,8 +254,9 @@ public class VaultListController implements FxController { Vault preparedVault = prepareVault(selectedDirectory, vaultComponentFactory, mountServices); - if (vaultListManager.get(preparedVault.getPath()).isPresent()) { - dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, vaultListManager.get(preparedVault.getPath()).get().getDisplayName()) // + Optional matchingVaultListEntry = vaultListManager.get(preparedVault.getPath()); + if (matchingVaultListEntry.isPresent()) { + dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, matchingVaultListEntry.get().getDisplayName()) // .setOkAction(Stage::close) // .build().showAndWait(); break; From b00ee4740a8cb52a09ee090d23ef0f40264f9111 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Mon, 7 Jul 2025 14:58:19 +0200 Subject: [PATCH 052/117] legacy vaults supported now --- .../common/recovery/BackupRestorer.java | 6 ++-- .../common/recovery/MasterkeyService.java | 18 ++++++---- .../common/vaults/VaultListManager.java | 33 ++++++++----------- .../ui/mainwindow/VaultListController.java | 2 +- .../RecoveryKeyResetPasswordController.java | 27 +++++++-------- .../RecoveryKeyValidateController.java | 16 ++++----- .../fxml/recoverykey_reset_password.fxml | 4 +-- 7 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java index bbb1fa4c9..e5afc5112 100644 --- a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java +++ b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java @@ -18,7 +18,7 @@ public final class BackupRestorer { private BackupRestorer() {} - public static void restoreIfPresent(Path vaultPath, String filePrefix) { + public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) { Path targetFile = vaultPath.resolve(filePrefix); try (Stream files = Files.list(vaultPath)) { @@ -45,7 +45,9 @@ public final class BackupRestorer { private static void copyBackupFile(Path backupFile, Path configPath) { try { Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING); + LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath); } catch (IOException e) { - LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e); } + LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e); + } } } diff --git a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java index 5fd74a7f0..d434b2a36 100644 --- a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java +++ b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java @@ -19,7 +19,6 @@ import java.nio.file.StandardOpenOption; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.stream.Stream; @@ -64,19 +63,22 @@ public final class MasterkeyService { public static Optional detect(Masterkey masterkey, Path vaultPath) { try (Stream paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) { - List excludedFilenames = List.of("dirid.c9r", "dir.c9r"); - Optional c9rFile = paths.filter(p -> p.toString().endsWith(".c9r")).filter(p -> excludedFilenames.stream().noneMatch(p.toString()::endsWith)).findFirst(); + Optional c9rFile = paths // + .filter(p -> p.toString().endsWith(".c9r")) // + .filter(p -> !p.toString().equals("dir.c9r")) // + .findFirst(); if (c9rFile.isEmpty()) { LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath); return Optional.empty(); } - return determineScheme(c9rFile.get(), masterkey); // + return determineScheme(c9rFile.get(), masterkey); } catch (IOException e) { LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e); return Optional.empty(); } } + private static Optional determineScheme(Path c9rFile, Masterkey masterkey) { return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> { try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) { @@ -90,9 +92,13 @@ public final class MasterkeyService { headerBuf.flip(); cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate()); + LOG.debug("Detected Crypto scheme: {}", scheme); return true; - } catch (IOException | CryptoException | NoSuchAlgorithmException e) { - LOG.info("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e); + } catch (CryptoException e) { + LOG.debug("Could not decrypt with scheme: {}", scheme); + return false; + } catch (IOException | NoSuchAlgorithmException e) { + LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e); return false; } }).findFirst(); diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 9a0b7977e..34ee47b45 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -23,13 +23,7 @@ import java.util.ResourceBundle; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; -import static org.cryptomator.common.vaults.VaultState.Value.ERROR; -import static org.cryptomator.common.vaults.VaultState.Value.LOCKED; -import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING; -import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION; -import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING; -import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED; -import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; +import static org.cryptomator.common.vaults.VaultState.Value.*; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Constants; @@ -194,31 +188,32 @@ public class VaultListManager { Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME); if (!Files.exists(pathToVault)) { - return VaultState.Value.MISSING; + return MISSING; } - BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VAULTCONFIG_FILENAME); - - BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), MASTERKEY_FILENAME); + if(!Files.exists(pathToVaultConfig)) { + BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME); + } + if(!Files.exists(pathToMasterkey)){ + BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME); + } if (!Files.exists(pathToVaultConfig) && !Files.exists(pathToMasterkey)) { return ALL_MISSING; } - + var checkedDirStructureVaultState = checkDirStructure(pathToVault); if (!Files.exists(pathToVaultConfig)) { - return VAULT_CONFIG_MISSING; + return checkedDirStructureVaultState.equals(LOCKED) ? VAULT_CONFIG_MISSING : checkedDirStructureVaultState ; } - return checkDirStructure(pathToVault); + return checkedDirStructureVaultState; } private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException { return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) { - case VAULT -> VaultState.Value.LOCKED; - case UNRELATED -> VaultState.Value.MISSING; - case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? // - VaultState.Value.NEEDS_MIGRATION // - : VaultState.Value.MISSING; + case VAULT -> LOCKED; + case UNRELATED -> MISSING; + case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING; }; } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index 3d63fd6be..0e2065c62 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -270,7 +270,7 @@ public class VaultListController implements FxController { recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow(); case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow(); - case LOCKED -> { + case LOCKED, NEEDS_MIGRATION -> { vaultListManager.addVault(preparedVault); dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait(); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 460372ca1..c810d5f22 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -65,7 +65,6 @@ public class RecoveryKeyResetPasswordController implements FxController { private final Stage owner; public NewPasswordController newPasswordController; - public Button backButton; public Button nextButton; @Inject @@ -104,18 +103,8 @@ public class RecoveryKeyResetPasswordController implements FxController { @FXML public void initialize() { switch (recoverType.get()) { - case RESTORE_MASTERKEY -> { - nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); - nextButton.setOnAction((_) -> resetPassword()); - } - case RESTORE_ALL -> { - nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); - nextButton.setOnAction((_) -> restorePassword()); - } - case RESET_PASSWORD -> { - nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn")); - nextButton.setOnAction((_) -> resetPassword()); - } + case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); + case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn")); } } @@ -128,15 +117,21 @@ public class RecoveryKeyResetPasswordController implements FxController { } } + @FXML + public void next() { + switch (recoverType.get()) { + case RESTORE_ALL -> restorePassword(); + case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword(); + } + } + @FXML public void restorePassword() { try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) { Path recoveryPath = recoveryDirectory.getRecoveryPath(); MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters()); - Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME); - - try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) { + try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) { CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get()); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java index 163d734ed..2e292d539 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java @@ -1,10 +1,11 @@ package org.cryptomator.ui.recoverykey; - import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import org.cryptomator.common.Nullable; import org.cryptomator.common.ObservableUtil; +import org.cryptomator.common.recovery.MasterkeyService; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfigLoadException; @@ -26,12 +27,9 @@ import javafx.scene.control.TextFormatter; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import org.cryptomator.common.recovery.MasterkeyService; -import org.cryptomator.common.recovery.RecoveryActionType; - public class RecoveryKeyValidateController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); + private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class); private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' ')); private final Vault vault; @@ -137,7 +135,7 @@ public class RecoveryKeyValidateController implements FxController { private void validateRecoveryKey() { switch (recoverType.get()) { case RESTORE_ALL, RESTORE_VAULT_CONFIG -> { - try{ + try { var combo = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess); combo.ifPresent(cipherCombo::set); if (combo.isPresent()) { @@ -145,15 +143,13 @@ public class RecoveryKeyValidateController implements FxController { } else { recoveryKeyState.set(RecoveryKeyState.WRONG); } - } - catch (IllegalArgumentException e){ + } catch (IllegalArgumentException e) { recoveryKeyState.set(RecoveryKeyState.INVALID); } } case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> { isWrongKey = false; - boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), - unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null); + boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null); if (valid) { recoveryKeyState.set(RecoveryKeyState.CORRECT); } else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig() diff --git a/src/main/resources/fxml/recoverykey_reset_password.fxml b/src/main/resources/fxml/recoverykey_reset_password.fxml index a6b255ed8..648e761fd 100644 --- a/src/main/resources/fxml/recoverykey_reset_password.fxml +++ b/src/main/resources/fxml/recoverykey_reset_password.fxml @@ -24,8 +24,8 @@ - - - + + + + + + + - + diff --git a/src/main/resources/fxml/convertvault_hubtopassword_start.fxml b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml index d5c0a5e0b..4cdf76fc4 100644 --- a/src/main/resources/fxml/convertvault_hubtopassword_start.fxml +++ b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml @@ -5,29 +5,39 @@ - + + + + spacing="12"> - + + + + + + + + + + + - - - -