From 90cbb0c5f6f159eeae1cccf8513383e55833425a Mon Sep 17 00:00:00 2001 From: Jan-Peter Klein Date: Mon, 19 May 2025 14:27:05 +0200 Subject: [PATCH] new recovery package in common --- .../common/recovery/BackupRestorer.java | 52 ++++++++ .../common/recovery/CryptoFsInitializer.java | 33 +++++ .../common/recovery/MasterkeyService.java | 102 +++++++++++++++ .../common/recovery/RecoveryActionType.java | 9 ++ .../common/recovery/RecoveryDirectory.java | 65 +++++++++ .../common/vaults/VaultListManager.java | 71 +++++----- .../ChooseExistingVaultController.java | 45 ++++++- .../ui/convertvault/ConvertVaultModule.java | 4 +- .../VaultDetailMissingVaultController.java | 6 +- .../ui/recoverykey/RecoveryKeyComponent.java | 4 +- .../RecoveryKeyIsHubVaultController.java | 8 +- .../ui/recoverykey/RecoveryKeyModule.java | 4 +- .../RecoveryKeyRecoverController.java | 4 +- .../RecoveryKeyResetPasswordController.java | 123 +++++++++++++----- .../RecoveryKeyValidateController.java | 10 +- .../MasterkeyOptionsController.java | 6 +- .../fxml/recoverykey_reset_password.fxml | 3 +- src/main/resources/i18n/strings.properties | 1 + 18 files changed, 456 insertions(+), 94 deletions(-) create mode 100644 src/main/java/org/cryptomator/common/recovery/BackupRestorer.java create mode 100644 src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java create mode 100644 src/main/java/org/cryptomator/common/recovery/MasterkeyService.java create mode 100644 src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java create mode 100644 src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java diff --git a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java new file mode 100644 index 000000000..b61e29c10 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java @@ -0,0 +1,52 @@ +package org.cryptomator.common.recovery; + +import static org.cryptomator.common.vaults.VaultState.Value.*; + +import java.io.IOException; +import java.nio.file.*; +import java.util.stream.Stream; + +import org.cryptomator.common.vaults.VaultState.Value; + +public final class BackupRestorer { + + private BackupRestorer() {} + + public static boolean restoreIfPresent(Path vaultPath, Value vaultState) { + Path targetFile; + switch (vaultState) { + case VAULT_CONFIG_MISSING -> targetFile = vaultPath.resolve("vault.cryptomator"); + case MASTERKEY_MISSING -> targetFile = vaultPath.resolve("masterkey.cryptomator"); + default -> { + return false; + } + } + + try (Stream files = Files.list(vaultPath)) { + return files + .filter(file -> isValidBackupFileForState(file.getFileName().toString(), vaultState)) + .findFirst() + .map(backupFile -> copyBackupFile(backupFile, targetFile)) + .orElse(false); + } catch (IOException e) { + return false; + } + } + + private static boolean isValidBackupFileForState(String fileName, Value vaultState) { + return switch (vaultState) { + case VAULT_CONFIG_MISSING -> fileName.startsWith("vault.cryptomator") && fileName.endsWith(".bkup"); + case MASTERKEY_MISSING -> fileName.startsWith("masterkey.cryptomator") && fileName.endsWith(".bkup"); + default -> false; + }; + } + + private static boolean copyBackupFile(Path backupFile, Path configPath) { + try { + Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java new file mode 100644 index 000000000..c69e8dad8 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java @@ -0,0 +1,33 @@ +package org.cryptomator.common.recovery; + +import java.io.IOException; +import java.nio.file.Path; + +import javafx.beans.property.IntegerProperty; + +import org.cryptomator.common.Constants; +import org.cryptomator.cryptofs.CryptoFileSystemProperties; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.*; + +import static org.cryptomator.common.Constants.DEFAULT_KEY_ID; + +public final class CryptoFsInitializer { + + private CryptoFsInitializer() {} + + public static void init(Path recoveryPath, + Masterkey masterkey, + IntegerProperty shorteningThreshold, + CryptorProvider.Scheme scheme) throws IOException, CryptoException { + + MasterkeyLoader loader = ignored -> masterkey.copy(); + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties // + .cryptoFileSystemProperties() // + .withCipherCombo(scheme) // + .withKeyLoader(loader) // + .withShorteningThreshold(shorteningThreshold.get()) // + .build(); + CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID); + } +} diff --git a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java new file mode 100644 index 000000000..b91b032dc --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java @@ -0,0 +1,102 @@ +package org.cryptomator.common.recovery; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; + +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptolib.api.*; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javafx.beans.property.StringProperty; + +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; + +public final class MasterkeyService { + + private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class); + + private MasterkeyService() {} + + public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException { + recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword); + } + + public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException { + return masterkeyFileAccess.load(masterkeyFilePath, password); + } + + public static Optional validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, MasterkeyFileAccess masterkeyFileAccess, AtomicBoolean illegalArgumentExceptionOccurred) { + + var tmpPass = UUID.randomUUID().toString(); + try(RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) { + var tempRecoveryPath = recoveryDirectory.getRecoveryPath(); + recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, tempRecoveryPath, tmpPass); + var masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME); + + try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) { + return detect(mk.getEncoded(), vault.getPath()); + } catch (IOException | CryptoException e) { + LOG.info("Recovery key validation failed", e); + return Optional.empty(); + } catch (IllegalArgumentException e) { + illegalArgumentExceptionOccurred.set(true); + return Optional.empty(); + } + } catch (IOException | CryptoException e) { + LOG.info("Recovery key validation failed"); + } catch (IllegalArgumentException e) { + LOG.info("Recovery key has an illegal argument"); + illegalArgumentExceptionOccurred.set(true); + } + return Optional.empty(); + + } + public static Optional detect(byte[] masterkey, Path vaultPath) { + try (Stream paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) { + Path c9rFile = paths.filter(p -> p.toString().endsWith(".c9r")) + .findFirst().orElse(null); + if (c9rFile == null) { + LOG.info("No *.c9r file found in {}", vaultPath); + return Optional.empty(); + } + return determineScheme(c9rFile, masterkey); // jetzt auch ein Optional + } catch (IOException e) { + LOG.debug("Failed to inspect vault", e); + return Optional.empty(); + } + } + + private static Optional determineScheme(Path c9rFile, byte[] masterkey) { + try { + ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile)); + return Arrays.stream(CryptorProvider.Scheme.values()) + .filter(s -> tryDecrypt(header, new Masterkey(masterkey), s)) + .findFirst(); + } catch (IOException e) { + LOG.info("Failed to decrypt .c9r file", e); + return Optional.empty(); + } + } + + private static boolean tryDecrypt(ByteBuffer header, Masterkey masterkey, CryptorProvider.Scheme scheme) { + try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey, SecureRandom.getInstanceStrong())) { + cryptor.fileHeaderCryptor().decryptHeader(header.duplicate()); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java new file mode 100644 index 000000000..32f28d613 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java @@ -0,0 +1,9 @@ +package org.cryptomator.common.recovery; + +public enum RecoveryActionType { + RESTORE_VAULT_CONFIG, + RESTORE_MASTERKEY, + RESET_PASSWORD, + SHOW_KEY, + CONVERT_VAULT +} diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java new file mode 100644 index 000000000..e57ca4f1d --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java @@ -0,0 +1,65 @@ +package org.cryptomator.common.recovery; + +import java.io.IOException; +import java.nio.file.*; +import java.util.Comparator; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; + +public final class RecoveryDirectory implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class); + + private final Path recoveryPath; + private final Path vaultPath; + + + private static Path addR(Path p){ + return p.resolve("r"); + } + + private RecoveryDirectory(Path vaultPath) { + this.vaultPath = vaultPath; + this.recoveryPath = addR(vaultPath); + } + + public static RecoveryDirectory create(Path vaultPath) throws IOException { + //TODO: Files.createTmpDirectory Doku lesen und ggf nutzen + Path recovery = addR(vaultPath); + Files.createDirectory(recovery); + return new RecoveryDirectory(vaultPath); + } + + public void moveRecoveredFiles() 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)); //TODO: ? StandardCopyOption.REPLACE_EXISTING + } + + private void deleteRecoveryDirectory() { + try (var paths = Files.walk(recoveryPath)) { + paths.sorted(Comparator.reverseOrder()).forEach(p -> { //TODO: wieso reverseOrder + 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); + } + } + + @Override + public void close() { + deleteRecoveryDirectory(); + } + + public Path getRecoveryPath() { + return recoveryPath; + } + +} diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index e9b9d9e37..f3e42a797 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -26,9 +26,11 @@ 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.PROCESSING; +import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED; import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.BackupRestorer; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystemProvider; @@ -146,45 +148,35 @@ public class VaultListManager { return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault(); } } - public static VaultState.Value redetermineVaultState(Vault vault) { - VaultState state = vault.stateProperty(); - VaultState.Value previousState = state.getValue(); - return switch (previousState) { - case LOCKED, NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING -> { - try { - var determinedState = determineVaultState(vault.getPath(), vault.getVaultSettings()); - if (determinedState == MASTERKEY_MISSING) { - var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme(); - if (KeyLoadingStrategy.isHubVault(vaultScheme)) { - determinedState = LOCKED; - } - } - if (determinedState == LOCKED) { - vault.getVaultConfigCache().reloadConfig(); - } - state.set(determinedState); - yield determinedState; - } catch (IOException e) { - LOG.warn("Failed to determine vault state for {}", vault.getPath(), e); - state.set(ERROR); - vault.setLastKnownException(e); - yield ERROR; + VaultState state = vault.stateProperty(); + VaultState.Value previous = state.getValue(); + + if (previous.equals(UNLOCKED)||previous.equals(PROCESSING)) { + return previous; + } + + try { + VaultState.Value determined = determineVaultState(vault.getPath(), vault.getVaultSettings()); + + if (determined == MASTERKEY_MISSING) { + if (KeyLoadingStrategy.isHubVault(vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme())) { + determined = LOCKED; } } - case ERROR -> { - try { - var determinedState = determineVaultState(vault.getPath(), vault.getVaultSettings()); - state.set(determinedState); - yield determinedState; - } catch (IOException e) { - LOG.warn("Failed to redetermine vault state for {}", vault.getPath(), e); - vault.setLastKnownException(e); - yield ERROR; - } + + if (determined == LOCKED) { + vault.getVaultConfigCache().reloadConfig(); } - case UNLOCKED, PROCESSING -> previousState; - }; + + state.set(determined); + return determined; + } catch (IOException e) { + LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e); + vault.setLastKnownException(e); + state.set(ERROR); + return ERROR; + } } private static VaultState.Value determineVaultState(Path pathToVault, VaultSettings vaultSettings) throws IOException { @@ -195,9 +187,12 @@ public class VaultListManager { return VaultState.Value.MISSING; } - boolean vaultConfigRestored = Files.notExists(pathToVaultConfig) && RecoverUtil.restoreBackupIfAvailable(pathToVaultConfig, VaultState.Value.VAULT_CONFIG_MISSING); + boolean vaultConfigRestored = Files.notExists(pathToVaultConfig) + && BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VaultState.Value.VAULT_CONFIG_MISSING); - boolean masterkeyRestored = Files.notExists(pathToMasterkey) && KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get()) && RecoverUtil.restoreBackupIfAvailable(pathToMasterkey, VaultState.Value.MASTERKEY_MISSING); + boolean masterkeyRestored = Files.notExists(pathToMasterkey) + && KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get()) + && BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), VaultState.Value.MASTERKEY_MISSING); if (vaultConfigRestored || masterkeyRestored) { return LOCKED; diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 1b2f7e7e9..f040154ea 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -22,12 +22,15 @@ import java.util.Optional; import java.util.ResourceBundle; import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB; +import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; +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.integrations.mount.MountService; import org.cryptomator.integrations.uiappearance.Theme; @@ -131,14 +134,50 @@ public class ChooseExistingVaultController implements FxController { @FXML public void restoreVaultConfigWithRecoveryKey() { DirectoryChooser directoryChooser = new DirectoryChooser(); - Optional optionalVault = RecoverUtil.checkAndPrepareVaultFromDirectory(directoryChooser, window, dialogs, vaultComponentFactory, mountServices); + File selectedDirectory; + do { + selectedDirectory = directoryChooser.showDialog(window); + boolean hasSubfolderD = new File(selectedDirectory, "d").isDirectory(); + + if (!hasSubfolderD) { + dialogs.prepareNoDDirectorySelectedDialog(window).build().showAndWait(); + selectedDirectory = null; + } + } while (selectedDirectory == null); + + Optional optionalVault = prepareVault(selectedDirectory,vaultComponentFactory, + mountServices); + //TODO: optional raus, und mit error dialog arbeiten (UI kram in UI package!) hier nur fehler werfen optionalVault.ifPresent(vault -> { - ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_VAULT_CONFIG); + ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG); recoveryKeyWindow.create(vault, window, recoverTypeProperty).showIsHubVaultDialogWindow(); }); } + public static Optional prepareVault(File selectedDirectory, VaultComponent.Factory vaultComponentFactory, List mountServices) { + + 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(); //TODO: VAULT_CONFIG_MISSING nicht sicher, stand nochmal überprüfen + + //due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498 + 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); + } + /* Getter */ public ObservableValue screenshotProperty() { diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java index d0b60d0ac..a5fc761aa 100644 --- a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java @@ -4,7 +4,7 @@ import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.ui.changepassword.NewPasswordController; @@ -122,7 +122,7 @@ abstract class ConvertVaultModule { @IntoMap @FxControllerKey(RecoveryKeyValidateController.class) static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) { - return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, new SimpleObjectProperty<>(RecoverUtil.Type.CONVERT_VAULT), null, null); + return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), null, null); } } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index d7ac79f08..7f53346d7 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -1,6 +1,6 @@ package org.cryptomator.ui.mainwindow; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.ui.common.FxController; @@ -61,14 +61,14 @@ public class VaultDetailMissingVaultController implements FxController { dialogs.prepareContactHubAdmin(window).build().showAndWait(); } else { - ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_VAULT_CONFIG); + ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG); recoveryKeyWindow.create(vault.get(), window, recoverTypeProperty).showIsHubVaultDialogWindow(); } } @FXML void restoreMasterkey() { - ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESTORE_MASTERKEY); + ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY); recoveryKeyWindow.create(vault.get(), window, recoverTypeProperty).showRecoveryKeyRecoverWindow(); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index 6b799cee2..1c7419d9a 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -3,7 +3,7 @@ package org.cryptomator.ui.recoverykey; import dagger.BindsInstance; import dagger.Lazy; import dagger.Subcomponent; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -55,7 +55,7 @@ public interface RecoveryKeyComponent { RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, // @BindsInstance @Named("keyRecoveryOwner") Stage owner, // - @BindsInstance @Named("recoverType") ObjectProperty recoverType); + @BindsInstance @Named("recoverType") ObjectProperty recoverType); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java index 6110db9b5..4b0b440ec 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyIsHubVaultController.java @@ -9,7 +9,7 @@ import javafx.stage.Stage; import java.util.ResourceBundle; import dagger.Lazy; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -19,12 +19,12 @@ public class RecoveryKeyIsHubVaultController implements FxController { private final Stage window; private final Lazy recoverykeyRecoverScene; - private final ObjectProperty recoverType; + private final ObjectProperty recoverType; @Inject public RecoveryKeyIsHubVaultController(@RecoveryKeyWindow Stage window, // @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, // - @Named("recoverType") ObjectProperty recoverType, // + @Named("recoverType") ObjectProperty recoverType, // ResourceBundle resourceBundle) { this.window = window; window.setTitle(resourceBundle.getString("recoveryKey.recoverVaultConfig.title")); @@ -40,7 +40,7 @@ public class RecoveryKeyIsHubVaultController implements FxController { @FXML public void recover() { - recoverType.set(RecoverUtil.Type.RESTORE_VAULT_CONFIG); + recoverType.set(RecoveryActionType.RESTORE_VAULT_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 e48bc7825..f120e68b1 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -5,7 +5,7 @@ import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.Nullable; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.CryptorProvider; @@ -177,7 +177,7 @@ abstract class RecoveryKeyModule { @Provides @IntoMap @FxControllerKey(RecoveryKeyValidateController.class) - static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) { + static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) { return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, recoverType, cipherCombo, masterkeyFileAccess); } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 9264b4d22..ed1275d5a 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.recoverykey; import dagger.Lazy; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -27,7 +27,7 @@ public class RecoveryKeyRecoverController implements FxController { public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, // @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy expertSettingsScene, // - ResourceBundle resourceBundle, @Named("recoverType") ObjectProperty recoverType) { + ResourceBundle resourceBundle, @Named("recoverType") ObjectProperty recoverType) { this.window = window; this.nextScene = switch (recoverType.get()) { diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 4403c6697..640461251 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -5,6 +5,7 @@ import javax.inject.Named; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.concurrent.Task; @@ -19,7 +20,10 @@ import java.util.concurrent.ExecutorService; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; import dagger.Lazy; -import org.cryptomator.common.RecoverUtil; +import org.cryptomator.common.recovery.CryptoFsInitializer; +import org.cryptomator.common.recovery.MasterkeyService; +import org.cryptomator.common.recovery.RecoveryActionType; +import org.cryptomator.common.recovery.RecoveryDirectory; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; import org.cryptomator.cryptolib.api.CryptoException; @@ -50,7 +54,7 @@ public class RecoveryKeyResetPasswordController implements FxController { private final MasterkeyFileAccess masterkeyFileAccess; private final VaultListManager vaultListManager; private final IntegerProperty shorteningThreshold; - private final ObjectProperty recoverType; + private final ObjectProperty recoverType; private final ObjectProperty cipherCombo; private final ResourceBundle resourceBundle; private final StringProperty buttonText = new SimpleStringProperty(); @@ -70,7 +74,8 @@ public class RecoveryKeyResetPasswordController implements FxController { MasterkeyFileAccess masterkeyFileAccess, // VaultListManager vaultListManager, // @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // - @Named("recoverType") ObjectProperty recoverType, @Named("cipherCombo") ObjectProperty cipherCombo,// + @Named("recoverType") ObjectProperty recoverType, // + @Named("cipherCombo") ObjectProperty cipherCombo,// ResourceBundle resourceBundle, Dialogs dialogs) { this.window = window; this.vault = vault; @@ -90,8 +95,8 @@ public class RecoveryKeyResetPasswordController implements FxController { initButtonText(recoverType.get()); } - private void initButtonText(RecoverUtil.Type type) { - if (type == RecoverUtil.Type.RESTORE_MASTERKEY) { + private void initButtonText(RecoveryActionType type) { + if (type == RecoveryActionType.RESTORE_MASTERKEY) { buttonText.set(resourceBundle.getString("generic.button.close")); } else { buttonText.set(resourceBundle.getString("generic.button.back")); @@ -100,42 +105,68 @@ public class RecoveryKeyResetPasswordController implements FxController { @FXML public void close() { - if (recoverType.getValue().equals(RecoverUtil.Type.RESTORE_MASTERKEY)) { + if (recoverType.getValue().equals(RecoveryActionType.RESTORE_MASTERKEY)) { window.close(); } else { window.setScene(recoverExpertSettingsScene.get()); } } + @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())) { + CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold, cipherCombo.get()); + } + + recoveryDirectory.moveRecoveredFiles(); + + if (!vaultListManager.containsVault(vault.getPath())) { + vaultListManager.add(vault.getPath()); + } + + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) + .setTitleKey("recoveryKey.recoverVaultConfig.title") + .setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") + .build().showAndWait(); + window.close(); + + } catch (IOException | CryptoException e) { + LOG.error("Recovery process failed", e); + appWindows.showErrorWindow(e, window, null); + } + } @FXML public void resetPassword() { - if (vault.isMissingVaultConfig()) { - try { - Path recoveryPath = RecoverUtil.createRecoveryDirectory(vault.getPath()); - RecoverUtil.createNewMasterkeyFile(recoveryKeyFactory, recoveryPath, recoveryKey.get(), newPasswordController.passwordField.getCharacters()); - Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME); + Task task = new ResetPasswordTask(recoveryKeyFactory, vault, recoveryKey, newPasswordController); - try (Masterkey masterkey = RecoverUtil.loadMasterkey(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) { - RecoverUtil.initializeCryptoFileSystem(recoveryPath, masterkey, shorteningThreshold, cipherCombo.get()); - } + task.setOnScheduled(_ -> LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath())); - RecoverUtil.moveRecoveredFiles(recoveryPath, vault.getPath()); - RecoverUtil.deleteRecoveryDirectory(recoveryPath); - RecoverUtil.addVaultToList(vaultListManager, vault.getPath()); - - dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).setTitleKey("recoveryKey.recoverVaultConfig.title").setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message").build().showAndWait(); - window.close(); - - } catch (IOException | CryptoException e) { - LOG.error("Recovery process failed", e); + task.setOnSucceeded(_ -> { + LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath()); + if (vault.getState().equals(org.cryptomator.common.vaults.VaultState.Value.MASTERKEY_MISSING)) { + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) + .setTitleKey("recoveryKey.recoverMasterkey.title") + .setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message") + .build().showAndWait(); + } else { + dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle) + .build().showAndWait(); } - } else { - Task task = RecoverUtil.createResetPasswordTask( // - resourceBundle, owner, recoveryKeyFactory, // - vault, recoveryKey, newPasswordController, // - window, appWindows, dialogs); - executor.submit(task); - } + window.close(); + }); + + task.setOnFailed(_ -> { + LOG.error("Resetting password failed.", task.getException()); + appWindows.showErrorWindow(task.getException(), window, null); + }); + + executor.submit(task); } /* Getter/Setter */ @@ -155,5 +186,37 @@ public class RecoveryKeyResetPasswordController implements FxController { public boolean isPasswordSufficientAndMatching() { return newPasswordController.isGoodPassword(); } + private final ReadOnlyBooleanWrapper vaultConfigMissing = new ReadOnlyBooleanWrapper(); + public ReadOnlyBooleanProperty vaultConfigMissingProperty() { + return vaultConfigMissing.getReadOnlyProperty(); + } + + public boolean isVaultConfigMissing() { + return vault.isMissingVaultConfig(); + } + + private 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(_ -> 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; + } + } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java index a0e23db27..c579abf57 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java @@ -5,7 +5,6 @@ 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.RecoverUtil; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.VaultConfigLoadException; @@ -28,6 +27,9 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import java.util.concurrent.atomic.AtomicBoolean; +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); @@ -43,7 +45,7 @@ public class RecoveryKeyValidateController implements FxController { private final ObjectProperty recoveryKeyState; private final ObjectProperty cipherCombo; private final AutoCompleter autoCompleter; - private final ObjectProperty recoverType; + private final ObjectProperty recoverType; private final MasterkeyFileAccess masterkeyFileAccess; private volatile boolean isWrongKey; @@ -54,7 +56,7 @@ public class RecoveryKeyValidateController implements FxController { @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, // StringProperty recoveryKey, // RecoveryKeyFactory recoveryKeyFactory, // - @Named("recoverType") ObjectProperty recoverType, // + @Named("recoverType") ObjectProperty recoverType, // @Named("cipherCombo") ObjectProperty cipherCombo,// MasterkeyFileAccess masterkeyFileAccess) { this.vault = vault; @@ -137,7 +139,7 @@ public class RecoveryKeyValidateController implements FxController { switch (recoverType.get()) { case RESTORE_VAULT_CONFIG -> { AtomicBoolean illegalArgumentExceptionOccurred = new AtomicBoolean(false); - var combo = RecoverUtil.validateRecoveryKeyAndGetCombo( + var combo = MasterkeyService.validateRecoveryKeyAndDetectCombo( recoveryKeyFactory, vault, recoveryKey, masterkeyFileAccess, illegalArgumentExceptionOccurred); combo.ifPresent(cipherCombo::set); if (illegalArgumentExceptionOccurred.get()) { diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java index 36b70eb1d..c489f6230 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.vaultoptions; -import org.cryptomator.common.RecoverUtil; import org.cryptomator.common.keychain.KeychainManager; +import org.cryptomator.common.recovery.RecoveryActionType; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.changepassword.ChangePasswordComponent; import org.cryptomator.ui.common.FxController; @@ -50,13 +50,13 @@ public class MasterkeyOptionsController implements FxController { @FXML public void showRecoveryKey() { - ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.SHOW_KEY); + ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY); recoveryKeyWindow.create(vault, window, recoverTypeProperty).showRecoveryKeyCreationWindow(); } @FXML public void showRecoverVaultDialog() { - ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoverUtil.Type.RESET_PASSWORD); + ObjectProperty recoverTypeProperty = new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD); recoveryKeyWindow.create(vault, window, recoverTypeProperty).showRecoveryKeyRecoverWindow(); } diff --git a/src/main/resources/fxml/recoverykey_reset_password.fxml b/src/main/resources/fxml/recoverykey_reset_password.fxml index faf93cbf1..2ca139631 100644 --- a/src/main/resources/fxml/recoverykey_reset_password.fxml +++ b/src/main/resources/fxml/recoverykey_reset_password.fxml @@ -25,7 +25,8 @@