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