mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
remove RecoverUtil
This commit is contained in:
@@ -1,295 +0,0 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.concurrent.Task;
|
||||
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.ResourceBundle;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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.MASTERKEY_MISSING;
|
||||
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;
|
||||
|
||||
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.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
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;
|
||||
|
||||
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))) {
|
||||
Path c9rFile = paths.filter(path -> path.toString().endsWith(".c9r")).findFirst().orElseThrow(() -> new IllegalStateException("No .c9r file found. The vault might not exist or the provided masterkey does not match."));
|
||||
CryptorProvider.Scheme scheme = determineScheme(c9rFile, masterkey);
|
||||
if (scheme == null) {
|
||||
throw new IllegalArgumentException("Invalid masterkey: Decryption failed.");
|
||||
}
|
||||
return scheme;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to detect cipher combo.");
|
||||
}
|
||||
}
|
||||
|
||||
private static CryptorProvider.Scheme determineScheme(Path c9rFile, byte[] masterkey) {
|
||||
try {
|
||||
ByteBuffer header = ByteBuffer.wrap(Files.readAllBytes(c9rFile));
|
||||
if (tryDecrypt(header, new Masterkey(masterkey), SIV_GCM)) {
|
||||
return SIV_GCM;
|
||||
}
|
||||
if (tryDecrypt(header, new Masterkey(masterkey), SIV_CTRMAC)) {
|
||||
return SIV_CTRMAC;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to read .c9r file.", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean tryDecrypt(ByteBuffer header, Masterkey masterkey, CryptorProvider.Scheme scheme) {
|
||||
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey, SecureRandom.getInstanceStrong())) {
|
||||
cryptor.fileHeaderCryptor().decryptHeader(header.duplicate());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean matchesBackupFile(String fileName, VaultState.Value vaultState) {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean copyBackupFile(Path backupFile, Path configPath) {
|
||||
try {
|
||||
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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, Masterkey masterkey, IntegerProperty shorteningThreshold, CryptorProvider.Scheme combo) throws IOException, CryptoException {
|
||||
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 Optional<CryptorProvider.Scheme> getCipherCombo(Path vaultPath, Masterkey masterkey) {
|
||||
try {
|
||||
return Optional.of(RecoverUtil.detectCipherCombo(masterkey.getEncoded(), vaultPath));
|
||||
} catch (Exception e) {
|
||||
LOG.info("Failed to detect cipher combo.");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<CryptorProvider.Scheme> validateRecoveryKeyAndGetCombo(RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, MasterkeyFileAccess masterkeyFileAccess, AtomicBoolean illegalArgumentExceptionOccurred) {
|
||||
var tmpPass = "asdasdasd";
|
||||
|
||||
try {
|
||||
var tempRecoveryPath = createRecoveryDirectory(vault.getPath());
|
||||
try {
|
||||
createNewMasterkeyFile(recoveryKeyFactory, tempRecoveryPath, recoveryKey.get(), tmpPass);
|
||||
var masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (var masterkey = loadMasterkey(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
|
||||
return getCipherCombo(vault.getPath(), masterkey);
|
||||
}
|
||||
} finally {
|
||||
deleteRecoveryDirectory(tempRecoveryPath);
|
||||
}
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.info("Recovery key validation failed");
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.info("Recovery key has an illegal argument");
|
||||
illegalArgumentExceptionOccurred.set(true);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
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(ResourceBundle resourceBundle, Stage owner, RecoveryKeyFactory recoveryKeyFactory, Vault vault, StringProperty recoveryKey, NewPasswordController newPasswordController, Stage window, FxApplicationWindows appWindows, Dialogs dialogs) {
|
||||
|
||||
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(MASTERKEY_MISSING)) {
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).setTitleKey("recoveryKey.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait();
|
||||
window.close();
|
||||
} else {
|
||||
dialogs.prepareRecoverPasswordSuccess(window, owner, resourceBundle).build().showAndWait();
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
|
||||
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> checkAndPrepareVaultFromDirectory(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();
|
||||
|
||||
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
|
||||
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);
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
RESTORE_VAULT_CONFIG,
|
||||
RESTORE_MASTERKEY,
|
||||
RESET_PASSWORD,
|
||||
SHOW_KEY,
|
||||
CONVERT_VAULT;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user