legacy vaults supported now

This commit is contained in:
Jan-Peter Klein
2025-07-07 14:58:19 +02:00
parent f5ecf846f2
commit b00ee4740a
7 changed files with 50 additions and 56 deletions

View File

@@ -18,7 +18,7 @@ public final class BackupRestorer {
private BackupRestorer() {}
public static void restoreIfPresent(Path vaultPath, String filePrefix) {
public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) {
Path targetFile = vaultPath.resolve(filePrefix);
try (Stream<Path> files = Files.list(vaultPath)) {
@@ -45,7 +45,9 @@ public final class BackupRestorer {
private static void copyBackupFile(Path backupFile, Path configPath) {
try {
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath);
} catch (IOException e) {
LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e); }
LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e);
}
}
}

View File

@@ -19,7 +19,6 @@ import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
@@ -64,19 +63,22 @@ public final class MasterkeyService {
public static Optional<CryptorProvider.Scheme> detect(Masterkey masterkey, Path vaultPath) {
try (Stream<Path> paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
List<String> excludedFilenames = List.of("dirid.c9r", "dir.c9r");
Optional<Path> c9rFile = paths.filter(p -> p.toString().endsWith(".c9r")).filter(p -> excludedFilenames.stream().noneMatch(p.toString()::endsWith)).findFirst();
Optional<Path> c9rFile = paths //
.filter(p -> p.toString().endsWith(".c9r")) //
.filter(p -> !p.toString().equals("dir.c9r")) //
.findFirst();
if (c9rFile.isEmpty()) {
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
return Optional.empty();
}
return determineScheme(c9rFile.get(), masterkey); //
return determineScheme(c9rFile.get(), masterkey);
} catch (IOException e) {
LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e);
return Optional.empty();
}
}
private static Optional<CryptorProvider.Scheme> determineScheme(Path c9rFile, Masterkey masterkey) {
return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> {
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) {
@@ -90,9 +92,13 @@ public final class MasterkeyService {
headerBuf.flip();
cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate());
LOG.debug("Detected Crypto scheme: {}", scheme);
return true;
} catch (IOException | CryptoException | NoSuchAlgorithmException e) {
LOG.info("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
} catch (CryptoException e) {
LOG.debug("Could not decrypt with scheme: {}", scheme);
return false;
} catch (IOException | NoSuchAlgorithmException e) {
LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
return false;
}
}).findFirst();

View File

@@ -23,13 +23,7 @@ import java.util.ResourceBundle;
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.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.PROCESSING;
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.*;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Constants;
@@ -194,31 +188,32 @@ public class VaultListManager {
Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME);
if (!Files.exists(pathToVault)) {
return VaultState.Value.MISSING;
return MISSING;
}
BackupRestorer.restoreIfPresent(pathToVaultConfig.getParent(), VAULTCONFIG_FILENAME);
BackupRestorer.restoreIfPresent(pathToMasterkey.getParent(), MASTERKEY_FILENAME);
if(!Files.exists(pathToVaultConfig)) {
BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME);
}
if(!Files.exists(pathToMasterkey)){
BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME);
}
if (!Files.exists(pathToVaultConfig) && !Files.exists(pathToMasterkey)) {
return ALL_MISSING;
}
var checkedDirStructureVaultState = checkDirStructure(pathToVault);
if (!Files.exists(pathToVaultConfig)) {
return VAULT_CONFIG_MISSING;
return checkedDirStructureVaultState.equals(LOCKED) ? VAULT_CONFIG_MISSING : checkedDirStructureVaultState ;
}
return checkDirStructure(pathToVault);
return checkedDirStructureVaultState;
}
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;
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
VaultState.Value.NEEDS_MIGRATION //
: VaultState.Value.MISSING;
case VAULT -> LOCKED;
case UNRELATED -> MISSING;
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING;
};
}

View File

@@ -270,7 +270,7 @@ public class VaultListController implements FxController {
recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
case ALL_MISSING ->
recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
case LOCKED -> {
case LOCKED, NEEDS_MIGRATION -> {
vaultListManager.addVault(preparedVault);
dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait();
}

View File

@@ -65,7 +65,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
private final Stage owner;
public NewPasswordController newPasswordController;
public Button backButton;
public Button nextButton;
@Inject
@@ -104,18 +103,8 @@ public class RecoveryKeyResetPasswordController implements FxController {
@FXML
public void initialize() {
switch (recoverType.get()) {
case RESTORE_MASTERKEY -> {
nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
nextButton.setOnAction((_) -> resetPassword());
}
case RESTORE_ALL -> {
nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
nextButton.setOnAction((_) -> restorePassword());
}
case RESET_PASSWORD -> {
nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
nextButton.setOnAction((_) -> resetPassword());
}
case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
}
}
@@ -128,15 +117,21 @@ public class RecoveryKeyResetPasswordController implements FxController {
}
}
@FXML
public void next() {
switch (recoverType.get()) {
case RESTORE_ALL -> restorePassword();
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
}
}
@FXML
public void restorePassword() {
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path recoveryPath = recoveryDirectory.getRecoveryPath();
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
Path masterkeyFilePath = recoveryPath.resolve(MASTERKEY_FILENAME);
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, newPasswordController.passwordField.getCharacters())) {
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
}

View File

@@ -1,10 +1,11 @@
package org.cryptomator.ui.recoverykey;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.recovery.MasterkeyService;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
@@ -26,12 +27,9 @@ import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import org.cryptomator.common.recovery.MasterkeyService;
import org.cryptomator.common.recovery.RecoveryActionType;
public class RecoveryKeyValidateController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class);
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
private final Vault vault;
@@ -137,7 +135,7 @@ public class RecoveryKeyValidateController implements FxController {
private void validateRecoveryKey() {
switch (recoverType.get()) {
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
try{
try {
var combo = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess);
combo.ifPresent(cipherCombo::set);
if (combo.isPresent()) {
@@ -145,15 +143,13 @@ public class RecoveryKeyValidateController implements FxController {
} else {
recoveryKeyState.set(RecoveryKeyState.WRONG);
}
}
catch (IllegalArgumentException e){
} catch (IllegalArgumentException e) {
recoveryKeyState.set(RecoveryKeyState.INVALID);
}
}
case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> {
isWrongKey = false;
boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(),
unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
if (valid) {
recoveryKeyState.set(RecoveryKeyState.CORRECT);
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()

View File

@@ -24,8 +24,8 @@
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button fx:id="backButton" text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" text="%recoveryKey.recover.recoverBtn" ButtonBar.buttonData="FINISH" defaultButton="true" disable="${!controller.passwordSufficientAndMatching}"/>
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" ButtonBar.buttonData="FINISH" defaultButton="true" disable="${!controller.passwordSufficientAndMatching}" onAction="#next"/>
</buttons>
</ButtonBar>
</VBox>