From 8c5325511c4f39054443df56b11a06819fc0a636 Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Fri, 28 Feb 2025 13:06:35 +0100 Subject: [PATCH] created more static methods in RecoverUtil, added first flow for choose dir and restore --- .../org/cryptomator/common/RecoverUtil.java | 181 ++++++++++++++++-- .../common/vaults/VaultConfigCache.java | 2 +- .../common/vaults/VaultListManager.java | 6 + .../ChooseExistingVaultController.java | 43 ++++- .../org/cryptomator/ui/common/FxmlFile.java | 1 + .../org/cryptomator/ui/dialogs/Dialogs.java | 14 ++ .../ui/recoverykey/RecoveryKeyComponent.java | 6 +- .../RecoveryKeyExpertSettingsController.java | 97 ++++++++++ .../RecoveryKeyIsHubVaultController.java | 10 +- .../ui/recoverykey/RecoveryKeyModule.java | 23 +++ .../RecoveryKeyRecoverController.java | 18 +- .../RecoveryKeyResetPasswordController.java | 114 ++++------- ...veryKeyResetPasswordSuccessController.java | 12 +- .../MasterkeyOptionsController.java | 8 +- .../fxml/recoverykey_expert_settings.fxml | 85 ++++++++ src/main/resources/i18n/strings.properties | 10 + 16 files changed, 512 insertions(+), 118 deletions(-) create mode 100644 src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java create mode 100644 src/main/resources/fxml/recoverykey_expert_settings.fxml diff --git a/src/main/java/org/cryptomator/common/RecoverUtil.java b/src/main/java/org/cryptomator/common/RecoverUtil.java index 8cce5fe09..71a8bf4ed 100644 --- a/src/main/java/org/cryptomator/common/RecoverUtil.java +++ b/src/main/java/org/cryptomator/common/RecoverUtil.java @@ -1,30 +1,60 @@ package org.cryptomator.common; +import dagger.Lazy; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultComponent; +import org.cryptomator.common.vaults.VaultConfigCache; +import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.common.vaults.VaultState; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.integrations.mount.MountService; +import org.cryptomator.ui.changepassword.NewPasswordController; +import org.cryptomator.ui.dialogs.Dialogs; +import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.StringProperty; +import javafx.concurrent.Task; +import javafx.scene.Scene; +import javafx.stage.DirectoryChooser; +import javafx.stage.Stage; +import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.security.SecureRandom; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; import java.util.stream.Stream; +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; +import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; import static org.cryptomator.cryptolib.api.CryptorProvider.Scheme.SIV_CTRMAC; import static org.cryptomator.cryptolib.api.CryptorProvider.Scheme.SIV_GCM; public class RecoverUtil { + private static final Logger LOG = LoggerFactory.getLogger(RecoverUtil.class); + public static CryptorProvider.Scheme detectCipherCombo(byte[] masterkey, Path pathToVault) { try (Stream paths = Files.walk(pathToVault.resolve(DATA_DIR_NAME))) { - return paths.filter(path -> path.toString().endsWith(".c9r")) - .findFirst() - .map(c9rFile -> determineScheme(c9rFile, masterkey)) - .orElseThrow(() -> new IllegalStateException("No .c9r file found.")); + return paths.filter(path -> path.toString().endsWith(".c9r")).findFirst().map(c9rFile -> determineScheme(c9rFile, masterkey)).orElseThrow(() -> new IllegalStateException("No .c9r file found.")); } catch (IOException e) { throw new IllegalStateException("Failed to detect cipher combo.", e); } @@ -33,8 +63,7 @@ public class RecoverUtil { private static CryptorProvider.Scheme determineScheme(Path c9rFile, byte[] masterkey) { try { ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile)); - return tryDecrypt(header, new Masterkey(masterkey), SIV_GCM) ? SIV_GCM : - tryDecrypt(header, new Masterkey(masterkey), SIV_CTRMAC) ? SIV_CTRMAC : null; + return tryDecrypt(header, new Masterkey(masterkey), SIV_GCM) ? SIV_GCM : tryDecrypt(header, new Masterkey(masterkey), SIV_CTRMAC) ? SIV_CTRMAC : null; } catch (IOException e) { return null; } @@ -51,11 +80,7 @@ public class RecoverUtil { public static boolean restoreBackupIfAvailable(Path configPath, VaultState.Value vaultState) { try (Stream files = Files.list(configPath.getParent())) { - return files - .filter(file -> matchesBackupFile(file.getFileName().toString(), vaultState)) - .findFirst() - .map(backupFile -> copyBackupFile(backupFile, configPath)) - .orElse(false); + return files.filter(file -> matchesBackupFile(file.getFileName().toString(), vaultState)).findFirst().map(backupFile -> copyBackupFile(backupFile, configPath)).orElse(false); } catch (IOException e) { return false; } @@ -78,4 +103,138 @@ public class RecoverUtil { } } + public static Path createRecoveryDirectory(Path vaultPath) throws IOException { + Path recoveryPath = vaultPath.resolve("r"); + Files.createDirectory(recoveryPath); + return recoveryPath; + } + + public static void createNewMasterkeyFile(RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, String recoveryKey, CharSequence newPassword) throws IOException { + recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword); + } + + public static Masterkey loadMasterkey(org.cryptomator.cryptolib.common.MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException { + return masterkeyFileAccess.load(masterkeyFilePath, password); + } + + public static void initializeCryptoFileSystem(Path recoveryPath, Path vaultPath, Masterkey masterkey, IntegerProperty shorteningThreshold) throws IOException, CryptoException { + var combo = RecoverUtil.detectCipherCombo(masterkey.getEncoded(), vaultPath); + org.cryptomator.cryptolib.api.MasterkeyLoader loader = ignored -> masterkey.copy(); + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(combo).withKeyLoader(loader).withShorteningThreshold(shorteningThreshold.get()).build(); + CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID); + } + + public static void moveRecoveredFiles(Path recoveryPath, Path vaultPath) throws IOException { + Files.move(recoveryPath.resolve(MASTERKEY_FILENAME), vaultPath.resolve(MASTERKEY_FILENAME), StandardCopyOption.REPLACE_EXISTING); + Files.move(recoveryPath.resolve(VAULTCONFIG_FILENAME), vaultPath.resolve(VAULTCONFIG_FILENAME)); + } + + public static void deleteRecoveryDirectory(Path recoveryPath) { + 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); + } + }); + } catch (IOException e) { + LOG.error("Failed to clean up recovery directory", e); + } + } + + public static void addVaultToList(VaultListManager vaultListManager, Path vaultPath) throws IOException { + if (!vaultListManager.containsVault(vaultPath)) { + vaultListManager.add(vaultPath); + } + } + + public static Task createResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController, Stage window, Lazy recoverResetPasswordSuccessScene, Lazy recoverResetVaultConfigSuccessScene, FxApplicationWindows appWindows) { + + Task task = new ResetPasswordTask(recoveryKeyFactory, vault, recoveryKey, newPasswordController); + + task.setOnScheduled(_ -> { + LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath()); + }); + + task.setOnSucceeded(_ -> { + LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath()); + if (vault.getState().equals(VAULT_CONFIG_MISSING)) { + window.setScene(recoverResetVaultConfigSuccessScene.get()); + } else { + window.setScene(recoverResetPasswordSuccessScene.get()); + } + }); + + task.setOnFailed(_ -> { + LOG.error("Resetting password failed.", task.getException()); + appWindows.showErrorWindow(task.getException(), window, null); + }); + + return task; + } + + + public static class ResetPasswordTask extends Task { + + private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class); + private final RecoveryKeyFactory recoveryKeyFactory; + private final Vault vault; + private final StringProperty recoveryKey; + private final NewPasswordController newPasswordController; + + public ResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController) { + this.recoveryKeyFactory = recoveryKeyFactory; + this.vault = vault; + this.recoveryKey = recoveryKey; + this.newPasswordController = newPasswordController; + + setOnFailed(event -> LOG.error("Failed to reset password", getException())); + } + + @Override + protected Void call() throws IOException, IllegalArgumentException { + recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters()); + return null; + } + } + + public static Optional prepareVaultFromDirectory(DirectoryChooser directoryChooser, Stage window, Dialogs dialogs, VaultComponent.Factory vaultComponentFactory, List mountServices) { + + File selectedDirectory; + do { + selectedDirectory = directoryChooser.showDialog(window); + if (selectedDirectory == null) { + return Optional.empty(); + } + boolean hasSubfolderD = new File(selectedDirectory, "d").isDirectory(); + + if (!hasSubfolderD) { + dialogs.prepareNoDDirectorySelectedDialog(window).build().showAndWait(); + selectedDirectory = null; + } + } while (selectedDirectory == null); + + Path selectedPath = selectedDirectory.toPath(); + VaultSettings vaultSettings = VaultSettings.withRandomId(); + vaultSettings.path.set(selectedPath); + if (selectedPath.getFileName() != null) { + vaultSettings.displayName.set(selectedPath.getFileName().toString()); + } else { + vaultSettings.displayName.set("defaultVaultName"); + } + + var wrapper = new VaultConfigCache(vaultSettings); + Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, VAULT_CONFIG_MISSING, null).vault(); + + // Spezialbehandlung für Windows + Dropbox + WinFsp + var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider"; + if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) { + vaultSettings.mountService.setValue(nameOfWinfspLocalMounter); + } + + return Optional.of(vault); + } + + } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java index b879b1f81..4a95fe50b 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java @@ -20,7 +20,7 @@ public class VaultConfigCache { private final VaultSettings settings; private final AtomicReference config; - VaultConfigCache(VaultSettings settings) { + public VaultConfigCache(VaultSettings settings) { this.settings = settings; this.config = new AtomicReference<>(null); } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index 7f18e82c9..63e32da7d 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -69,6 +69,12 @@ public class VaultListManager { autoLocker.init(); } + public boolean containsVault(Path vaultPath) { + assert vaultPath.isAbsolute(); + assert vaultPath.normalize().equals(vaultPath); + return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath())); + } + public Vault add(Path pathToVault) throws IOException { Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath(); if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) { diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 71e525e42..f42eb3ac6 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -2,14 +2,19 @@ package org.cryptomator.ui.addvaultwizard; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.RecoverUtil; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.common.vaults.VaultComponent; import org.cryptomator.common.vaults.VaultListManager; +import org.cryptomator.integrations.mount.MountService; import org.cryptomator.integrations.uiappearance.Theme; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationStyle; import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,12 +24,15 @@ import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.scene.image.Image; +import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.ResourceBundle; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; @@ -42,6 +50,11 @@ public class ChooseExistingVaultController implements FxController { private final VaultListManager vaultListManager; private final ResourceBundle resourceBundle; private final ObservableValue screenshot; + private final Dialogs dialogs; + private final VaultComponent.Factory vaultComponentFactory; + private final RecoveryKeyComponent.Factory recoveryKeyWindow; + private final List mountServices; + @Inject ChooseExistingVaultController(@AddVaultWizardWindow Stage window, // @@ -51,7 +64,11 @@ public class ChooseExistingVaultController implements FxController { @AddVaultWizardWindow ObjectProperty vault, // VaultListManager vaultListManager, // ResourceBundle resourceBundle, // - FxApplicationStyle applicationStyle) { + FxApplicationStyle applicationStyle, // + RecoveryKeyComponent.Factory recoveryKeyWindow, // + VaultComponent.Factory vaultComponentFactory, // + List mountServices, // + Dialogs dialogs) { this.window = window; this.successScene = successScene; this.appWindows = appWindows; @@ -60,6 +77,10 @@ public class ChooseExistingVaultController implements FxController { this.vaultListManager = vaultListManager; this.resourceBundle = resourceBundle; this.screenshot = applicationStyle.appliedThemeProperty().map(this::selectScreenshot); + this.recoveryKeyWindow = recoveryKeyWindow; + this.vaultComponentFactory = vaultComponentFactory; + this.mountServices = mountServices; + this.dialogs = dialogs; } private Image selectScreenshot(Theme theme) { @@ -96,7 +117,24 @@ public class ChooseExistingVaultController implements FxController { @FXML public void restoreVaultConfigWithRecoveryKey() { - //appWindows.showErrorWindow(e, window, window.getScene()); + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(resourceBundle.getString("generic.button.cancel")); + + Optional optionalVault = RecoverUtil.prepareVaultFromDirectory(directoryChooser, window, dialogs, vaultComponentFactory, mountServices); + + optionalVault.ifPresent(vault -> { + dialogs.prepareContactHubAdmin(window) // + .setTitleKey("a.title", vault.getVaultSettings().displayName.get() + " " + vault.getState()) // + .setDescriptionKey("a.description") // + .setMessageKey("a.message") // + .setCancelButtonKey("generic.button.cancel") // + .setOkButtonKey("generic.button.next") // + .setOkAction(stage -> { + recoveryKeyWindow.create(vault, window).showIsHubVaultDialogWindow(); + stage.close(); + }) // + .build().showAndWait(); + }); } /* Getter */ @@ -109,5 +147,4 @@ public class ChooseExistingVaultController implements FxController { return screenshot.getValue(); } - } diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index 2e3c2513f..9a2a68329 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -41,6 +41,7 @@ public enum FxmlFile { QUIT_FORCED("/fxml/quit_forced.fxml"), // RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), // RECOVERYKEY_IS_HUB_VAULT("/fxml/recoverykey_is_hub_vault.fxml"), // + RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), // RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), // RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), // RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java index 5ac9914b6..5e625deb2 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -97,4 +97,18 @@ public class Dialogs { .setOkAction(okAction) // .setCancelAction(Stage::close); } + + public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("recoveryKey.noDDirDetected.title") // + .setMessageKey("recoveryKey.noDDirDetected.message") // + .setDescriptionKey("recoveryKey.noDDirDetected.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("generic.button.change") // + .setCancelButtonKey("generic.button.close") // + .setOkAction(Stage::close) // + .setCancelAction(Stage::close); + } + } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index 6c6e836db..40a180f0a 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -43,16 +43,16 @@ public interface RecoveryKeyComponent { default void showRecoveryKeyRecoverWindow(String title) { Stage stage = window(); - stage.setScene(recoverScene().get()); stage.setTitle(title); + stage.setScene(recoverScene().get()); stage.sizeToScene(); stage.show(); } - default void showIsHubVaultDialogWindow(){ + default void showIsHubVaultDialogWindow() { Stage stage = window(); stage.setScene(recoverIsHubVaultScene().get()); - stage.setTitle("Recover Vault Config"); + stage.setTitle("Recover Config"); stage.sizeToScene(); stage.show(); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java new file mode 100644 index 000000000..7f87c2bab --- /dev/null +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java @@ -0,0 +1,97 @@ +package org.cryptomator.ui.recoverykey; + +import dagger.Lazy; +import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.controls.NumericTextField; + +import javax.inject.Inject; +import javax.inject.Named; +import javafx.application.Application; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.IntegerProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.CheckBox; +import javafx.stage.Stage; + +@RecoveryKeyScoped +public class RecoveryKeyExpertSettingsController implements FxController { + + public static final int MAX_SHORTENING_THRESHOLD = 220; + public static final int MIN_SHORTENING_THRESHOLD = 36; + private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening"; + + private final Stage window; + private final Lazy application; + private final Lazy resetPasswordScene; + private final Lazy recoverScene; + + public CheckBox expertSettingsCheckBox; + public NumericTextField shorteningThresholdTextField; + private final IntegerProperty shorteningThreshold; + private final BooleanBinding validShorteningThreshold; + + + @Inject + public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, // + Lazy application, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverScene) { + this.window = window; + this.application = application; + this.resetPasswordScene = resetPasswordScene; + this.recoverScene = recoverScene; + this.shorteningThreshold = shorteningThreshold; + this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold); + + } + + @FXML + public void initialize() { + shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD); + shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD)); + shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> { + try { + int intValue = Integer.parseInt(newValue); + shorteningThreshold.set(intValue); + } catch (NumberFormatException e) { + shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException + } + }); + } + + @FXML + public void toggleUseExpertSettings() { + if (!expertSettingsCheckBox.isSelected()) { + shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD)); + } + } + + public void openDocs() { + application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL); + } + + public BooleanBinding validShorteningThresholdProperty() { + return validShorteningThreshold; + } + + public boolean isValidShorteningThreshold() { + var value = shorteningThreshold.get(); + return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD; + } + + @FXML + public void back() { + window.setScene(recoverScene.get()); + } + + @FXML + public void next() { + window.setScene(resetPasswordScene.get()); + } +} diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java index c0a8fdfd3..5469c4af9 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java @@ -1,7 +1,6 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; -import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -9,11 +8,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.stage.Stage; -import java.util.ResourceBundle; @RecoveryKeyScoped public class RecoveryKeyIsHubVaultController implements FxController { @@ -25,12 +22,8 @@ public class RecoveryKeyIsHubVaultController implements FxController { @Inject public RecoveryKeyIsHubVaultController(@RecoveryKeyWindow Stage window, - @RecoveryKeyWindow Vault vault, - @RecoveryKeyWindow StringProperty recoveryKey, - @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, - ResourceBundle resourceBundle) { + @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene) { this.window = window; - //window.setTitle("Is it a hub vault? Huh?"); this.recoverykeyRecoverScene = recoverykeyRecoverScene; } @@ -45,6 +38,7 @@ public class RecoveryKeyIsHubVaultController implements FxController { @FXML public void recover() { + window.setTitle("Recover Config"); window.setScene(recoverykeyRecoverScene.get()); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index 165787497..1f5e6b35d 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -7,6 +7,7 @@ import dagger.multibindings.IntoMap; import org.cryptomator.common.Nullable; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController; import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -19,6 +20,8 @@ import org.cryptomator.ui.common.StageFactory; import javax.inject.Named; import javax.inject.Provider; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.Scene; @@ -119,6 +122,13 @@ abstract class RecoveryKeyModule { return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_IS_HUB_VAULT); } + @Provides + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) + @RecoveryKeyScoped + static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS); + } + // ------------------ @Binds @@ -133,6 +143,19 @@ abstract class RecoveryKeyModule { return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization); } + @Provides + @Named("shorteningThreshold") + @RecoveryKeyScoped + static IntegerProperty provideShorteningThreshold() { + return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD); + } + + + @Binds + @IntoMap + @FxControllerKey(RecoveryKeyExpertSettingsController.class) + abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller); + @Binds @IntoMap @FxControllerKey(RecoveryKeyIsHubVaultController.class) diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index b22054f56..1890a2bd1 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -4,8 +4,6 @@ import dagger.Lazy; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.fxml.FXML; @@ -16,10 +14,8 @@ import java.util.ResourceBundle; @RecoveryKeyScoped public class RecoveryKeyRecoverController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); - private final Stage window; - private final Lazy resetPasswordScene; + private final Lazy nextScene; @FXML RecoveryKeyValidateController recoveryKeyValidateController; @@ -27,10 +23,16 @@ public class RecoveryKeyRecoverController implements FxController { @Inject public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, // @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy expertSettingsScene, // ResourceBundle resourceBundle) { this.window = window; - window.setTitle(resourceBundle.getString("recoveryKey.recover.title")); - this.resetPasswordScene = resetPasswordScene; + if (window.getTitle().equals("Recover Config")) { + this.nextScene = expertSettingsScene; + } else if (window.getTitle().equals(resourceBundle.getString("recoveryKey.recover.title"))) { + this.nextScene = resetPasswordScene; + } else { + this.nextScene = resetPasswordScene; + } } @FXML @@ -44,7 +46,7 @@ public class RecoveryKeyRecoverController implements FxController { @FXML public void recover() { - window.setScene(resetPasswordScene.get()); + window.setScene(nextScene.get()); } /* Getter/Setter */ diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 04439c1eb..b8b6fa259 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -3,23 +3,21 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; import org.cryptomator.common.RecoverUtil; import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultState; -import org.cryptomator.cryptofs.CryptoFileSystemProperties; -import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.common.vaults.VaultListManager; 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.changepassword.NewPasswordController; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.changepassword.NewPasswordController; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.StringProperty; import javafx.concurrent.Task; @@ -27,16 +25,10 @@ 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 { @@ -52,6 +44,8 @@ public class RecoveryKeyResetPasswordController implements FxController { private final Lazy recoverResetVaultConfigSuccessScene; private final FxApplicationWindows appWindows; private final MasterkeyFileAccess masterkeyFileAccess; + private final VaultListManager vaultListManager; + private final IntegerProperty shorteningThreshold; public NewPasswordController newPasswordController; @@ -64,7 +58,9 @@ public class RecoveryKeyResetPasswordController implements FxController { @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy recoverResetPasswordSuccessScene, // @FxmlScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS) Lazy recoverResetVaultConfigSuccessScene, // FxApplicationWindows appWindows, // - MasterkeyFileAccess masterkeyFileAccess) { + MasterkeyFileAccess masterkeyFileAccess, // + VaultListManager vaultListManager, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold) { this.window = window; this.vault = vault; this.recoveryKeyFactory = recoveryKeyFactory; @@ -74,6 +70,8 @@ public class RecoveryKeyResetPasswordController implements FxController { this.recoverResetVaultConfigSuccessScene = recoverResetVaultConfigSuccessScene; this.appWindows = appWindows; this.masterkeyFileAccess = masterkeyFileAccess; + this.vaultListManager = vaultListManager; + this.shorteningThreshold = shorteningThreshold; } @FXML @@ -83,82 +81,38 @@ public class RecoveryKeyResetPasswordController implements FxController { @FXML public void resetPassword() { - if(vault.isMissingVaultConfig()){ - Path vaultPath = vault.getPath(); - Path recoveryPath = vaultPath.resolve("r"); + if (vault.isMissingVaultConfig()) { 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 recoveryPath = RecoverUtil.createRecoveryDirectory(vault.getPath()); + RecoverUtil.createNewMasterkeyFile(recoveryKeyFactory, recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters()); + Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME); - Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME); - try (Masterkey masterkey = masterkeyFileAccess.load(masterkeyFilePath, newPasswordController.passwordField.getCharacters())) { - try { - var combo = RecoverUtil.detectCipherCombo(masterkey.getEncoded(),vaultPath); - MasterkeyLoader loader = ignored -> masterkey.copy(); - CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // - .withCipherCombo(combo) // - .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); - } - }); + try (Masterkey masterkey = RecoverUtil.loadMasterkey(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) { + RecoverUtil.initializeCryptoFileSystem(recoveryPath, vault.getPath(), masterkey, shorteningThreshold); } + + RecoverUtil.moveRecoveredFiles(recoveryPath, vault.getPath()); + RecoverUtil.deleteRecoveryDirectory(recoveryPath); + RecoverUtil.addVaultToList(vaultListManager, vault.getPath()); + window.setScene(recoverResetVaultConfigSuccessScene.get()); - } catch (IOException e) { - LOG.error("Moving recovered files failed", e); + } catch (IOException | CryptoException e) { + LOG.error("Recovery process 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()); - if(vault.getState().equals(VaultState.Value.VAULT_CONFIG_MISSING)){ - window.setScene(recoverResetVaultConfigSuccessScene.get()); - } - else { - window.setScene(recoverResetPasswordSuccessScene.get()); - } - }); - task.setOnFailed(event -> { - LOG.error("Resetting password failed.", task.getException()); - appWindows.showErrorWindow(task.getException(), window, null); - }); + } else { + Task task = RecoverUtil.createResetPasswordTask( // + recoveryKeyFactory, // + vault, // + recoveryKey, // + newPasswordController, // + window, // + recoverResetPasswordSuccessScene, // + recoverResetVaultConfigSuccessScene, // + appWindows); executor.submit(task); } } - private class ResetPasswordTask extends Task { - - private ResetPasswordTask() { - setOnFailed(event -> LOG.error("Failed to reset password", getException())); - } - - @Override - protected Void call() throws IOException, IllegalArgumentException { - recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters()); - return null; - } - - } - /* Getter/Setter */ public ReadOnlyBooleanProperty passwordSufficientAndMatchingProperty() { diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java index b8b106d8b..b646f0334 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java @@ -3,21 +3,29 @@ package org.cryptomator.ui.recoverykey; import org.cryptomator.ui.common.FxController; import javax.inject.Inject; +import javax.inject.Named; import javafx.fxml.FXML; import javafx.stage.Stage; @RecoveryKeyScoped -public class RecoveryKeyResetPasswordSuccessController implements FxController { +public class RecoveryKeyResetPasswordSuccessController implements FxController { private final Stage window; + private final Stage owner; @Inject - public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) { + public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window, // + @Named("keyRecoveryOwner") Stage owner) { + this.window = window; + this.owner = owner; } @FXML public void close() { + if (!owner.getTitle().equals("Cryptomator")) { + owner.close(); + } window.close(); } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java index dd003d93d..fe6ef60f0 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java @@ -14,6 +14,7 @@ import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.stage.Stage; +import java.util.ResourceBundle; @VaultOptionsScoped public class MasterkeyOptionsController implements FxController { @@ -27,16 +28,19 @@ public class MasterkeyOptionsController implements FxController { private final ForgetPasswordComponent.Builder forgetPasswordWindow; private final KeychainManager keychain; private final ObservableValue passwordSaved; + private final ResourceBundle resourceBundle; @Inject - MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) { + MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain, // + ResourceBundle resourceBundle) { this.vault = vault; this.window = window; this.changePasswordWindow = changePasswordWindow; this.recoveryKeyWindow = recoveryKeyWindow; this.forgetPasswordWindow = forgetPasswordWindow; this.keychain = keychain; + this.resourceBundle = resourceBundle; if (keychain.isSupported() && !keychain.isLocked()) { this.passwordSaved = keychain.getPassphraseStoredProperty(vault.getId()).orElse(false); } else { @@ -56,7 +60,7 @@ public class MasterkeyOptionsController implements FxController { @FXML public void showRecoverVaultDialog() { - recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow(); + recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow(resourceBundle.getString("recoveryKey.recover.title")); } @FXML diff --git a/src/main/resources/fxml/recoverykey_expert_settings.fxml b/src/main/resources/fxml/recoverykey_expert_settings.fxml new file mode 100644 index 000000000..c568a1cbc --- /dev/null +++ b/src/main/resources/fxml/recoverykey_expert_settings.fxml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +