瀏覽代碼

restore config by recoverykey
auto restore when bkup exists
added new VaultStates

Jan-Peter Klein 8 月之前
父節點
當前提交
ba3667ab51

+ 20 - 0
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> 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;
 	}

+ 56 - 2
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<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)) {

+ 4 - 0
src/main/java/org/cryptomator/common/vaults/VaultState.java

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

+ 2 - 1
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

+ 2 - 0
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;

+ 14 - 1
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> 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

+ 2 - 0
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;

+ 7 - 0
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 {

+ 73 - 14
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<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> {

+ 2 - 0
src/main/resources/fxml/vault_detail.fxml

@@ -53,5 +53,7 @@
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing.fxml" visible="${controller.vault.missing}" managed="${controller.vault.missing}"/>
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_needsmigration.fxml" visible="${controller.vault.needsMigration}" managed="${controller.vault.needsMigration}"/>
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unknownerror.fxml" visible="${controller.vault.unknownError}" managed="${controller.vault.unknownError}"/>
+		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_masterkey.fxml" visible="${controller.vault.missingMasterkey}" managed="${controller.vault.missingMasterkey}"/>
+		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_vault_config.fxml" visible="${controller.vault.missingVaultConfig}" managed="${controller.vault.missingVaultConfig}"/>
 	</children>
 </VBox>

+ 44 - 0
src/main/resources/fxml/vault_detail_missing_masterkey.fxml

@@ -0,0 +1,44 @@
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.StackPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.shape.Circle?>
+<VBox xmlns:fx="http://javafx.com/fxml"
+	  xmlns="http://javafx.com/javafx"
+	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
+	  alignment="TOP_CENTER"
+	  spacing="9">
+	<children>
+		<VBox spacing="9" alignment="CENTER">
+			<StackPane alignment="CENTER">
+				<Circle styleClass="glyph-icon-primary" radius="48"/>
+				<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE" glyphSize="48"/>
+				<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="SEARCH" glyphSize="24">
+					<StackPane.margin>
+						<Insets top="12"/>
+					</StackPane.margin>
+				</FontAwesome5IconView>
+			</StackPane>
+			<Label text="%main.vaultDetail.missingMasterkey.info" wrapText="true"/>
+		</VBox>
+		<VBox spacing="6" alignment="CENTER">
+			<Button text="%main.vaultDetail.missing.recheck" minWidth="120" onAction="#recheck">
+				<graphic>
+					<FontAwesome5IconView glyph="REDO"/>
+				</graphic>
+			</Button>
+			<Button text="%main.vaultDetail.missingMasterkey.restore" minWidth="120" onAction="#restoreMasterkey">
+				<graphic>
+					<FontAwesome5IconView glyph="MAGIC"/>
+				</graphic>
+			</Button>
+			<Button text="%main.vaultDetail.missing.remove" minWidth="120" onAction="#didClickRemoveVault">
+				<graphic>
+					<FontAwesome5IconView glyph="TRASH"/>
+				</graphic>
+			</Button>
+		</VBox>
+	</children>
+</VBox>

+ 44 - 0
src/main/resources/fxml/vault_detail_missing_vault_config.fxml

@@ -0,0 +1,44 @@
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.StackPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.shape.Circle?>
+<VBox xmlns:fx="http://javafx.com/fxml"
+	  xmlns="http://javafx.com/javafx"
+	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
+	  alignment="TOP_CENTER"
+	  spacing="9">
+	<children>
+		<VBox spacing="9" alignment="CENTER">
+			<StackPane alignment="CENTER">
+				<Circle styleClass="glyph-icon-primary" radius="48"/>
+				<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE" glyphSize="48"/>
+				<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="SEARCH" glyphSize="24">
+					<StackPane.margin>
+						<Insets top="12"/>
+					</StackPane.margin>
+				</FontAwesome5IconView>
+			</StackPane>
+			<Label text="%main.vaultDetail.missingVaultConfig.info" wrapText="true"/>
+		</VBox>
+		<VBox spacing="6" alignment="CENTER">
+			<Button text="%main.vaultDetail.missing.recheck" minWidth="120" onAction="#recheck">
+				<graphic>
+					<FontAwesome5IconView glyph="REDO"/>
+				</graphic>
+			</Button>
+			<Button text="%main.vaultDetail.missingVaultConfig.restore" minWidth="120" onAction="#restoreVaultConfig">
+				<graphic>
+					<FontAwesome5IconView glyph="MAGIC"/>
+				</graphic>
+			</Button>
+			<Button text="%main.vaultDetail.missing.remove" minWidth="120" onAction="#didClickRemoveVault">
+				<graphic>
+					<FontAwesome5IconView glyph="TRASH"/>
+				</graphic>
+			</Button>
+		</VBox>
+	</children>
+</VBox>

+ 7 - 0
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