created more static methods in RecoverUtil, added first flow for choose dir and restore

This commit is contained in:
Jan-Peter Klein
2025-02-28 13:06:35 +01:00
parent 895614353e
commit 8c5325511c
16 changed files with 512 additions and 118 deletions

View File

@@ -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<Path> 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<Path> 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<Void> createResetPasswordTask(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController, Stage window, Lazy<Scene> recoverResetPasswordSuccessScene, Lazy<Scene> recoverResetVaultConfigSuccessScene, FxApplicationWindows appWindows) {
Task<Void> 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<Void> {
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<Vault> prepareVaultFromDirectory(DirectoryChooser directoryChooser, Stage window, Dialogs dialogs, VaultComponent.Factory vaultComponentFactory, List<MountService> 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);
}
}

View File

@@ -20,7 +20,7 @@ public class VaultConfigCache {
private final VaultSettings settings;
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
VaultConfigCache(VaultSettings settings) {
public VaultConfigCache(VaultSettings settings) {
this.settings = settings;
this.config = new AtomicReference<>(null);
}

View File

@@ -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) {

View File

@@ -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<Image> screenshot;
private final Dialogs dialogs;
private final VaultComponent.Factory vaultComponentFactory;
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final List<MountService> mountServices;
@Inject
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, //
@@ -51,7 +64,11 @@ public class ChooseExistingVaultController implements FxController {
@AddVaultWizardWindow ObjectProperty<Vault> vault, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle, //
FxApplicationStyle applicationStyle) {
FxApplicationStyle applicationStyle, //
RecoveryKeyComponent.Factory recoveryKeyWindow, //
VaultComponent.Factory vaultComponentFactory, //
List<MountService> 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<Vault> 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();
}
}

View File

@@ -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"), //

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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> application;
private final Lazy<Scene> resetPasswordScene;
private final Lazy<Scene> recoverScene;
public CheckBox expertSettingsCheckBox;
public NumericTextField shorteningThresholdTextField;
private final IntegerProperty shorteningThreshold;
private final BooleanBinding validShorteningThreshold;
@Inject
public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
Lazy<Application> application, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> 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());
}
}

View File

@@ -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<Scene> recoverykeyRecoverScene,
ResourceBundle resourceBundle) {
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> 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());
}
}

View File

@@ -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)

View File

@@ -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<Scene> resetPasswordScene;
private final Lazy<Scene> 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<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> 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 */

View File

@@ -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<Scene> 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<Scene> recoverResetPasswordSuccessScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_VAULT_CONFIG_SUCCESS) Lazy<Scene> 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<Void> 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<Void> task = RecoverUtil.createResetPasswordTask( //
recoveryKeyFactory, //
vault, //
recoveryKey, //
newPasswordController, //
window, //
recoverResetPasswordSuccessScene, //
recoverResetVaultConfigSuccessScene, //
appWindows);
executor.submit(task);
}
}
private class ResetPasswordTask extends Task<Void> {
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() {

View File

@@ -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();
}

View File

@@ -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<Boolean> 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