restore config by recoverykey

auto restore when bkup exists
added new VaultStates
This commit is contained in:
Jan-Peter Klein
2024-12-11 16:30:56 +01:00
parent 5114e2ad22
commit ba3667ab51
13 changed files with 277 additions and 18 deletions

View File

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

View File

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

View File

@@ -25,6 +25,10 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
*/
MISSING,
VAULT_CONFIG_MISSING,
MASTERKEY_MISSING,
/**
* Vault requires migration to a newer vault format
*/

View File

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

View File

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

View File

@@ -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> vault, RemoveVaultComponent.Builder removeVault, ResourceBundle resourceBundle, @MainWindow Stage window) {
public VaultDetailMissingVaultController(ObjectProperty<Vault> 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

View File

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

View File

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

View File

@@ -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<Scene> 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<Scene> 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<Scene> 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<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());
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<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());
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<Void> {