Explorar o código

add RecoverUtil and some ui changes

Jan-Peter Klein hai 3 meses
pai
achega
676c507a50

+ 86 - 0
src/main/java/org/cryptomator/common/RecoverUtil.java

@@ -0,0 +1,86 @@
+package org.cryptomator.common;
+
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.CryptorProvider;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+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.Optional;
+import java.util.stream.Stream;
+
+import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
+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 {
+
+	public static CryptorProvider.Scheme detectCipherCombo(byte[] masterkey, Path pathToVault) {
+		try {
+			Path dDirPath = pathToVault.resolve(DATA_DIR_NAME);
+
+			Optional<Path> firstC9rFile;
+			try (Stream<Path> paths = Files.walk(dDirPath)) {
+				firstC9rFile = paths.filter(path -> path.toString().endsWith(".c9r")).findFirst();
+			}
+			if (firstC9rFile.isEmpty()) {
+				throw new IllegalStateException("No .c9r file found.");
+			}
+
+			Path c9rFile = firstC9rFile.get();
+			if (canDecryptFileHeader(c9rFile, new Masterkey(masterkey), SIV_GCM)) {
+				return SIV_GCM;
+			}
+			if (canDecryptFileHeader(c9rFile, new Masterkey(masterkey), SIV_CTRMAC)) {
+				return SIV_CTRMAC;
+			}
+
+			return null;
+		} catch (IOException e) {
+			throw new IllegalStateException("Failed to detect cipher combo.", e);
+		}
+	}
+
+	private static boolean canDecryptFileHeader(Path c9rFile, Masterkey masterkey, CryptorProvider.Scheme scheme) {
+		try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey, SecureRandom.getInstanceStrong())) {
+			ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile));
+			cryptor.fileHeaderCryptor().decryptHeader(header);
+			return true;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+
+	public static VaultState.Value tryBackUpConfig(Path pathToConfig, VaultState.Value vaultState) {
+		try (Stream<Path> files = Files.list(pathToConfig.getParent())) {
+			Path backupFile = files.filter(file -> {
+				String fileName = file.getFileName().toString();
+				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;
+				};
+			}).findFirst().orElse(null);
+
+			if (backupFile != null) {
+				try {
+					Files.copy(backupFile, pathToConfig, StandardCopyOption.REPLACE_EXISTING);
+					return LOCKED;
+				} catch (IOException e) {
+					return vaultState;
+				}
+			} else {
+				return vaultState;
+			}
+		} catch (IOException e) {
+			return vaultState;
+		}
+	}
+
+}

+ 23 - 54
src/main/java/org/cryptomator/common/vaults/VaultListManager.java

@@ -9,13 +9,14 @@
 package org.cryptomator.common.vaults;
 
 import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.RecoverUtil;
 import org.cryptomator.common.settings.Settings;
 import org.cryptomator.common.settings.VaultSettings;
 import org.cryptomator.cryptofs.CryptoFileSystemProvider;
 import org.cryptomator.cryptofs.DirStructure;
 import org.cryptomator.cryptofs.migration.Migrators;
 import org.cryptomator.integrations.mount.MountService;
-import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
+import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -26,13 +27,11 @@ 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.Objects;
 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;
@@ -124,11 +123,15 @@ public class VaultListManager {
 	private Vault create(VaultSettings vaultSettings) {
 		var wrapper = new VaultConfigCache(vaultSettings);
 		try {
-			if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
-				var keyIdScheme = wrapper.get().getKeyId().getScheme();
-				vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
+			try {
+				if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
+					var keyIdScheme = wrapper.get().getKeyId().getScheme();
+					vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
+				}
+			} catch (NoSuchFileException e) {
+				LOG.warn("Vault config file not found.");
 			}
-			var vaultState = determineVaultState(vaultSettings.path.get());
+			var vaultState = determineVaultState(vaultSettings.path.get(),vaultSettings);
 			if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
 				wrapper.reloadConfig();
 			}
@@ -145,10 +148,10 @@ public class VaultListManager {
 		return switch (previousState) {
 			case LOCKED, NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, MASTERKEY_MISSING -> {
 				try {
-					var determinedState = determineVaultState(vault.getPath());
+					var determinedState = determineVaultState(vault.getPath(),vault.getVaultSettings());
 					if(determinedState == MASTERKEY_MISSING){
 						var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
-						if((vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){
+						if(KeyLoadingStrategy.isHubVault(vaultScheme)){
 							determinedState = LOCKED;
 						}
 					}
@@ -168,58 +171,24 @@ public class VaultListManager {
 		};
 	}
 
-	private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
+	private static VaultState.Value determineVaultState(Path pathToVault, VaultSettings vaultSettings) throws IOException {
 		Path pathToVaultConfig = Path.of(pathToVault.toString(),"vault.cryptomator");
 		Path pathToMasterkey = Path.of(pathToVault.toString(),"masterkey.cryptomator");
+
 		if (!Files.exists(pathToVault)) {
 			return VaultState.Value.MISSING;
 		}
-		else 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.notExists(pathToVaultConfig)) {
+			return RecoverUtil.tryBackUpConfig(pathToVaultConfig, VAULT_CONFIG_MISSING);
 		}
-		else if (!Files.exists(pathToMasterkey)) {
-			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.notExists(pathToMasterkey) &&
+				KeyLoadingStrategy.isMasterkeyFileVault(vaultSettings.lastKnownKeyLoader.get())) {
+			return RecoverUtil.tryBackUpConfig(pathToMasterkey, MASTERKEY_MISSING);
 		}
+		return checkDirStructure(pathToVault);
+	}
+
+	private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException{
 		return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
 			case VAULT -> VaultState.Value.LOCKED;
 			case UNRELATED -> VaultState.Value.MISSING;

+ 5 - 0
src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java

@@ -94,6 +94,11 @@ public class ChooseExistingVaultController implements FxController {
 		}
 	}
 
+	@FXML
+	public void restoreVaultConfigWithRecoveryKey() {
+		//appWindows.showErrorWindow(e, window, window.getScene());
+	}
+
 	/* Getter */
 
 	public ObservableValue<Image> screenshotProperty() {

+ 10 - 0
src/main/java/org/cryptomator/ui/dialogs/Dialogs.java

@@ -47,6 +47,16 @@ public class Dialogs {
 				});
 	}
 
+	public SimpleDialog.Builder prepareContactHubAdmin(Stage window) {
+		return createDialogBuilder().setOwner(window) //
+				.setTitleKey("contactHubAdmin.title") //
+				.setMessageKey("contactHubAdmin.message") //
+				.setDescriptionKey("contactHubAdmin.description") //
+				.setIcon(FontAwesome5Icon.EXCLAMATION)//
+				.setOkButtonKey("removeVault.confirmBtn") //
+				.setCancelButtonKey("generic.button.cancel");
+	}
+
 	public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
 		return createDialogBuilder() //
 				.setOwner(window) //

+ 7 - 1
src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java

@@ -4,6 +4,7 @@ import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.common.vaults.VaultListManager;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.dialogs.Dialogs;
+import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
 import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
 
 import javax.inject.Inject;
@@ -54,7 +55,12 @@ public class VaultDetailMissingVaultController implements FxController {
 
 	@FXML
 	void restoreVaultConfig() {
-		recoveryKeyWindow.create(vault.get(), window).showIsHubVaultDialogWindow();
+		if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){
+			dialogs.prepareContactHubAdmin(window).build().showAndWait();
+		}
+		else {
+			recoveryKeyWindow.create(vault.get(), window).showIsHubVaultDialogWindow();
+		}
 	}
 
 	@FXML

+ 3 - 7
src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java

@@ -1,9 +1,6 @@
 package org.cryptomator.ui.recoverykey;
 
 import dagger.Lazy;
-import org.cryptomator.common.Nullable;
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.cryptofs.VaultConfig;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxmlFile;
 import org.cryptomator.ui.common.FxmlScene;
@@ -11,9 +8,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
-import javafx.beans.Observable;
-import javafx.beans.property.StringProperty;
-import javafx.beans.value.ObservableValue;
 import javafx.fxml.FXML;
 import javafx.scene.Scene;
 import javafx.stage.Stage;
@@ -31,7 +25,9 @@ public class RecoveryKeyRecoverController implements FxController {
 	RecoveryKeyValidateController recoveryKeyValidateController;
 
 	@Inject
-	public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey,  @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
+	public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
+										@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
+										ResourceBundle resourceBundle) {
 		this.window = window;
 		window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
 		this.resetPasswordScene = resetPasswordScene;

+ 3 - 1
src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java

@@ -1,6 +1,7 @@
 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;
@@ -95,9 +96,10 @@ public class RecoveryKeyResetPasswordController implements FxController {
 			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(CryptorProvider.Scheme.SIV_CTRMAC) //
+							.withCipherCombo(combo) //
 							.withKeyLoader(loader) //
 							.withShorteningThreshold(220) //
 							.build();

+ 5 - 4
src/main/resources/fxml/addvault_existing.fxml

@@ -11,7 +11,7 @@
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
 	  prefWidth="450"
-	  prefHeight="450"
+	  prefHeight="480"
 	  spacing="24"
 	  alignment="CENTER">
 	<padding>
@@ -20,13 +20,14 @@
 	<children>
 		<ImageView VBox.vgrow="ALWAYS" fitWidth="400" preserveRatio="true" smooth="true" image="${controller.screenshot}"/>
 
-		<Label text="%addvaultwizard.existing.instruction" wrapText="true" labelFor="$finishButton"/>
+		<Label text="%addvaultwizard.existing.instruction" wrapText="true"/>
 
 		<Region VBox.vgrow="ALWAYS"/>
 
-		<ButtonBar buttonMinWidth="120" buttonOrder="+X">
+		<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
 			<buttons>
-				<Button fx:id="finishButton" text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/>
+				<Button text="%addvaultwizard.existing.restore" ButtonBar.buttonData="NEXT_FORWARD" onAction="#restoreVaultConfigWithRecoveryKey" />
+				<Button text="%addvaultwizard.existing.chooseBtn" ButtonBar.buttonData="NEXT_FORWARD" onAction="#chooseFileAndNext" defaultButton="true"/>
 			</buttons>
 		</ButtonBar>
 	</children>

+ 7 - 1
src/main/resources/i18n/strings.properties

@@ -95,7 +95,8 @@ addvault.new.readme.accessLocation.3=Any files added to this volume will be encr
 addvault.new.readme.accessLocation.4=Feel free to remove this file.
 ## Existing
 addvaultwizard.existing.title=Add Existing Vault
-addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
+addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead. If your vault.cryptomator lost you can recover it with your RecoveryKey.
+addvaultwizard.existing.restore=Restore…
 addvaultwizard.existing.chooseBtn=Choose…
 addvaultwizard.existing.filePickerTitle=Select Vault File
 addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
@@ -109,6 +110,11 @@ removeVault.message=Remove vault?
 removeVault.description=This will only make Cryptomator forget about this vault. You can add it again. No encrypted files will be deleted from your hard drive.
 removeVault.confirmBtn=Remove Vault
 
+# Contact Hub Admin
+contactHubAdmin.title=Contact Admin
+contactHubAdmin.message=Contact Admin
+contactHubAdmin.description=You should contact your vault admin.
+
 # Change Password
 changepassword.title=Change Password
 changepassword.enterOldPassword=Enter the current password for "%s"