diff --git a/src/main/java/org/cryptomator/common/Constants.java b/src/main/java/org/cryptomator/common/Constants.java index 5069002e7..105fba3d6 100644 --- a/src/main/java/org/cryptomator/common/Constants.java +++ b/src/main/java/org/cryptomator/common/Constants.java @@ -1,5 +1,9 @@ package org.cryptomator.common; +import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; + +import java.net.URI; + public interface Constants { String MASTERKEY_FILENAME = "masterkey.cryptomator"; @@ -7,6 +11,7 @@ public interface Constants { String VAULTCONFIG_FILENAME = "vault.cryptomator"; String CRYPTOMATOR_FILENAME_EXT = ".cryptomator"; String CRYPTOMATOR_FILENAME_GLOB = "*.cryptomator"; + URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); byte[] PEPPER = new byte[0]; } diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java index c6acbadf6..02327aaf4 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java @@ -11,8 +11,8 @@ import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; -import org.cryptomator.ui.common.PasswordStrengthUtil; +import org.cryptomator.ui.changepassword.NewPasswordController; +import org.cryptomator.ui.changepassword.PasswordStrengthUtil; import org.cryptomator.ui.common.StageFactory; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController; diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java index 0148686f3..81c6ce2da 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java @@ -13,7 +13,7 @@ import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; +import org.cryptomator.ui.changepassword.NewPasswordController; import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; @@ -48,13 +48,13 @@ import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.cryptomator.common.Constants.DEFAULT_KEY_ID; import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; @AddVaultWizardScoped public class CreateNewVaultPasswordController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class); - private static final URI DEFAULT_KEY_ID = URI.create(MasterkeyFileLoadingStrategy.SCHEME + ":" + MASTERKEY_FILENAME); // TODO better place? private final Stage window; private final Lazy chooseLocationScene; diff --git a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java index 200a70328..4d9666785 100644 --- a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java +++ b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordController.java @@ -9,7 +9,6 @@ import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.NewPasswordController; import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; diff --git a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java index d95b19410..947f87aeb 100644 --- a/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java +++ b/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java @@ -10,8 +10,6 @@ import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; -import org.cryptomator.ui.common.PasswordStrengthUtil; import org.cryptomator.ui.common.StageFactory; import javax.inject.Named; diff --git a/src/main/java/org/cryptomator/ui/common/NewPasswordController.java b/src/main/java/org/cryptomator/ui/changepassword/NewPasswordController.java similarity index 94% rename from src/main/java/org/cryptomator/ui/common/NewPasswordController.java rename to src/main/java/org/cryptomator/ui/changepassword/NewPasswordController.java index 6f029efe1..99e43f623 100644 --- a/src/main/java/org/cryptomator/ui/common/NewPasswordController.java +++ b/src/main/java/org/cryptomator/ui/changepassword/NewPasswordController.java @@ -1,5 +1,7 @@ -package org.cryptomator.ui.common; +package org.cryptomator.ui.changepassword; +import org.cryptomator.common.Passphrase; +import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.NiceSecurePasswordField; @@ -91,4 +93,8 @@ public class NewPasswordController implements FxController { return passwordStrength.get(); } + public Passphrase getNewPassword() { + return passwordField.getCharacters(); + } + } diff --git a/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java b/src/main/java/org/cryptomator/ui/changepassword/PasswordStrengthUtil.java similarity index 98% rename from src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java rename to src/main/java/org/cryptomator/ui/changepassword/PasswordStrengthUtil.java index 0224118cd..202e2f9cb 100644 --- a/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java +++ b/src/main/java/org/cryptomator/ui/changepassword/PasswordStrengthUtil.java @@ -6,7 +6,7 @@ * Contributors: * Jean-Noël Charon - initial API and implementation *******************************************************************************/ -package org.cryptomator.ui.common; +package org.cryptomator.ui.changepassword; import com.nulabinc.zxcvbn.Zxcvbn; import org.cryptomator.common.Environment; diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index c5b66498a..3bec75899 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -9,6 +9,9 @@ public enum FxmlFile { ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), // ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), // CHANGEPASSWORD("/fxml/changepassword.fxml"), // + CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), // + CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), // + CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), // ERROR("/fxml/error.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // HEALTH_START("/fxml/health_start.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index 60f37719b..77c3cb042 100644 --- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -18,6 +18,7 @@ public enum FontAwesome5Icon { COPY("\uF0C5"), // CROWN("\uF521"), // EDIT("\uF044"), // + EXCHANGE_ALT("\uF362"), // EXCLAMATION("\uF12A"), // EXCLAMATION_CIRCLE("\uF06A"), // EXCLAMATION_TRIANGLE("\uF071"), // diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultComponent.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultComponent.java new file mode 100644 index 000000000..5b162c070 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultComponent.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.ui.convertvault; + +import dagger.BindsInstance; +import dagger.Lazy; +import dagger.Subcomponent; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import javax.inject.Named; +import javafx.scene.Scene; +import javafx.stage.Stage; + +@ConvertVaultScoped +@Subcomponent(modules = {ConvertVaultModule.class}) +public interface ConvertVaultComponent { + + @ConvertVaultWindow + Stage window(); + + @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START) + Lazy hubToPasswordScene(); + + default void showHubToPasswordWindow() { + Stage stage = window(); + stage.setScene(hubToPasswordScene().get()); + stage.sizeToScene(); + stage.show(); + } + + @Subcomponent.Factory + interface Factory { + + ConvertVaultComponent create(@BindsInstance @ConvertVaultWindow Vault vault, @BindsInstance @Named("convertVaultOwner") Stage owner); + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java new file mode 100644 index 000000000..f70242d2b --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java @@ -0,0 +1,126 @@ +package org.cryptomator.ui.convertvault; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoMap; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.ui.changepassword.NewPasswordController; +import org.cryptomator.ui.changepassword.PasswordStrengthUtil; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxControllerKey; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; +import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController; + +import javax.inject.Named; +import javax.inject.Provider; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.Scene; +import javafx.stage.Modality; +import javafx.stage.Stage; +import java.io.IOException; +import java.util.Map; +import java.util.ResourceBundle; + +@Module +abstract class ConvertVaultModule { + + //TODO: if this fails, we cannot display an error + @Provides + @ConvertVaultWindow + @ConvertVaultScoped + static VaultConfig.UnverifiedVaultConfig vaultConfig(@ConvertVaultWindow Vault vault) { + try { + return vault.getVaultConfigCache().get(); + } catch (IOException e) { + return null; + } + } + + @Provides + @ConvertVaultWindow + @ConvertVaultScoped + static StringProperty provideRecoveryKeyProperty() { + return new SimpleStringProperty(); + } + + @Provides + @ConvertVaultWindow + @ConvertVaultScoped + static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); + } + + @Provides + @ConvertVaultWindow + @ConvertVaultScoped + static Stage provideStage(StageFactory factory, @Named("convertVaultOwner") Stage owner, ResourceBundle resourceBundle) { + Stage stage = factory.create(); + stage.setResizable(false); + stage.initModality(Modality.WINDOW_MODAL); + stage.initOwner(owner); + stage.setTitle(resourceBundle.getString("convertVault.title")); + return stage; + } + + @Provides + @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START) + @ConvertVaultScoped + static Scene provideHubToPasswordStartScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_START); + } + + @Provides + @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT) + @ConvertVaultScoped + static Scene provideHubToPasswordConvertScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT); + } + + @Provides + @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS) + @ConvertVaultScoped + static Scene provideHubToPasswordSuccessScene(@ConvertVaultWindow FxmlLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS); + } + + // ------------------ + + @Binds + @IntoMap + @FxControllerKey(HubToPasswordStartController.class) + abstract FxController bindHubToPasswordStartController(HubToPasswordStartController controller); + + @Binds + @IntoMap + @FxControllerKey(HubToPasswordConvertController.class) + abstract FxController bindHubToPasswordConvertController(HubToPasswordConvertController controller); + + @Binds + @IntoMap + @FxControllerKey(HubToPasswordSuccessController.class) + abstract FxController bindHubToPasswordSuccessController(HubToPasswordSuccessController controller); + + + @Provides + @IntoMap + @FxControllerKey(NewPasswordController.class) + static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) { + return new NewPasswordController(resourceBundle, strengthRater); + } + + @Provides + @IntoMap + @FxControllerKey(RecoveryKeyValidateController.class) + static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) { + return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory); + } + +} diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultScoped.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultScoped.java new file mode 100644 index 000000000..45ff780ae --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.convertvault; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface ConvertVaultScoped { + +} diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultWindow.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultWindow.java new file mode 100644 index 000000000..4ea9dc935 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultWindow.java @@ -0,0 +1,14 @@ +package org.cryptomator.ui.convertvault; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface ConvertVaultWindow { + +} diff --git a/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java new file mode 100644 index 000000000..51ff65ec1 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordConvertController.java @@ -0,0 +1,171 @@ +package org.cryptomator.ui.convertvault; + +import com.google.common.base.Preconditions; +import dagger.Lazy; +import org.cryptomator.common.Constants; +import org.cryptomator.common.Passphrase; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.VaultVersionMismatchException; +import org.cryptomator.cryptofs.common.BackupHelper; +import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.ui.changepassword.NewPasswordController; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.fxapp.FxApplicationWindows; +import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ContentDisplay; +import javafx.stage.Stage; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutorService; + +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.WRITE; +import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX; +import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; +import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME; + +public class HubToPasswordConvertController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(HubToPasswordConvertController.class); + + private final Stage window; + private final Lazy successScene; + private final FxApplicationWindows applicationWindows; + private final Vault vault; + private final StringProperty recoveryKey; + private final RecoveryKeyFactory recoveryKeyFactory; + private final MasterkeyFileAccess masterkeyFileAccess; + private final ExecutorService backgroundExecutorService; + private final ResourceBundle resourceBundle; + private final BooleanProperty conversionStarted; + + @FXML + NewPasswordController newPasswordController; + public Button convertBtn; + + @Inject + public HubToPasswordConvertController(@ConvertVaultWindow Stage window, @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_SUCCESS) Lazy successScene, FxApplicationWindows applicationWindows, @ConvertVaultWindow Vault vault, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess, ExecutorService backgroundExecutorService, ResourceBundle resourceBundle) { + this.window = window; + this.successScene = successScene; + this.applicationWindows = applicationWindows; + this.vault = vault; + this.recoveryKey = recoveryKey; + this.recoveryKeyFactory = recoveryKeyFactory; + this.masterkeyFileAccess = masterkeyFileAccess; + this.backgroundExecutorService = backgroundExecutorService; + this.resourceBundle = resourceBundle; + this.conversionStarted = new SimpleBooleanProperty(false); + + } + + @FXML + public void initialize() { + convertBtn.disableProperty().bind(Bindings.createBooleanBinding( // + () -> !newPasswordController.isGoodPassword() || conversionStarted.get(), // + newPasswordController.goodPasswordProperty(), // + conversionStarted)); + convertBtn.contentDisplayProperty().bind(Bindings.createObjectBinding( // + () -> conversionStarted.getValue() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY, // + conversionStarted)); + convertBtn.textProperty().bind(Bindings.createStringBinding( // + () -> resourceBundle.getString("convertVault.convert.convertBtn." + (conversionStarted.get() ? "processing" : "before")), // + conversionStarted)); + } + + @FXML + public void close() { + window.close(); + } + + @FXML + public void convert() { + Preconditions.checkState(newPasswordController.isGoodPassword()); + LOG.info("Converting access method of vault {} from hub to password", vault.getPath()); + CompletableFuture.runAsync(() -> conversionStarted.setValue(true), Platform::runLater) // + .thenRunAsync(this::convertInternal, backgroundExecutorService) // + .whenCompleteAsync((result, exception) -> { + if (exception == null) { + LOG.info("Conversion of vault {} succeeded.", vault.getPath()); + window.setScene(successScene.get()); + } else { + LOG.error("Conversion of vault {} failed.", vault.getPath(), exception); + applicationWindows.showErrorWindow(exception, window, null); + } + }, Platform::runLater); // + } + + //visible for testing + void convertInternal() throws CompletionException, IllegalArgumentException { + var passphrase = newPasswordController.getNewPassword(); + var vaultPath = vault.getPath(); + try { + //create masterkey + recoveryKeyFactory.newMasterkeyFileWithPassphrase(vaultPath, recoveryKey.get(), passphrase); + LOG.debug("Successfully created masterkey file for vault {}", vaultPath); + //create password config + Path passwordConfigPath = vaultPath.resolve("passwordBased." + VAULTCONFIG_FILENAME + ".tmp"); + passwordConfigPath = createPasswordConfig(passwordConfigPath, vaultPath.resolve(MASTERKEY_FILENAME), passphrase); + //backup hub config + var hubConfigPath = vaultPath.resolve(VAULTCONFIG_FILENAME); + backupHubConfig(hubConfigPath); + //replace hub by password + Files.move(passwordConfigPath, hubConfigPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (MasterkeyLoadingFailedException e) { + throw new CompletionException(new IOException("Vault conversion failed", e)); + } catch (IOException e) { + throw new CompletionException("Vault conversion failed", e); + } finally { + passphrase.destroy(); + } + } + + //visible for testing + void backupHubConfig(Path hubConfigPath) throws IOException { + byte[] hubConfigBytes = Files.readAllBytes(hubConfigPath); + Path backupPath = hubConfigPath.resolveSibling(VAULTCONFIG_FILENAME + BackupHelper.generateFileIdSuffix(hubConfigBytes) + MASTERKEY_BACKUP_SUFFIX); + Files.copy(hubConfigPath, backupPath, StandardCopyOption.REPLACE_EXISTING); + LOG.debug("Successfully created hub config backup {}", backupPath.getFileName()); + } + + //visible for testing + Path createPasswordConfig(Path passwordConfigPath, Path masterkeyFile, Passphrase passphrase) throws IOException, MasterkeyLoadingFailedException { + var unverifiedVaultConfig = vault.getVaultConfigCache().get(); + try (var masterkey = masterkeyFileAccess.load(masterkeyFile, passphrase)) { + var hubConfig = unverifiedVaultConfig.verify(masterkey.getEncoded(), unverifiedVaultConfig.allegedVaultVersion()); + var passwordConfig = VaultConfig.createNew() // + .cipherCombo(hubConfig.getCipherCombo()) // + .shorteningThreshold(hubConfig.getShorteningThreshold()) // + .build(); + if (passwordConfig.getVaultVersion() != hubConfig.getVaultVersion()) { + throw new VaultVersionMismatchException("Only vaults of version " + passwordConfig.getVaultVersion() + " can be converted."); + } + var token = passwordConfig.toToken(Constants.DEFAULT_KEY_ID.toString(), masterkey.getEncoded()); + Files.writeString(passwordConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW); + LOG.debug("Successfully created password config {}", passwordConfigPath); + return passwordConfigPath; + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordStartController.java b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordStartController.java new file mode 100644 index 000000000..04d7167c2 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordStartController.java @@ -0,0 +1,47 @@ +package org.cryptomator.ui.convertvault; + +import dagger.Lazy; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController; + +import javax.inject.Inject; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class HubToPasswordStartController implements FxController { + + private final Stage window; + private final Lazy convertScene; + + @FXML + RecoveryKeyValidateController recoveryKeyValidateController; + + @Inject + public HubToPasswordStartController(@ConvertVaultWindow Stage window, @FxmlScene(FxmlFile.CONVERTVAULT_HUBTOPASSWORD_CONVERT) Lazy convertScene) { + this.window = window; + this.convertScene = convertScene; + } + + @FXML + public void initialize() { + } + + @FXML + public void close() { + window.close(); + } + + @FXML + public void next() { + window.setScene(convertScene.get()); + } + + /* Getter/Setter */ + + public RecoveryKeyValidateController getValidateController() { + return recoveryKeyValidateController; + } +} diff --git a/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordSuccessController.java b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordSuccessController.java new file mode 100644 index 000000000..3aee13383 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/convertvault/HubToPasswordSuccessController.java @@ -0,0 +1,32 @@ +package org.cryptomator.ui.convertvault; + +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxController; + +import javax.inject.Inject; +import javafx.fxml.FXML; +import javafx.stage.Stage; + +public class HubToPasswordSuccessController implements FxController { + + private final Stage window; + private final Vault vault; + + @Inject + HubToPasswordSuccessController(@ConvertVaultWindow Stage window, @ConvertVaultWindow Vault vault) { + this.window = window; + this.vault = vault; + } + + @FXML + public void close() { + window.close(); + window.getOwner().hide(); + } + + /* Observables */ + + public Vault getVault() { + return vault; + } +} diff --git a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java b/src/main/java/org/cryptomator/ui/error/ErrorComponent.java similarity index 83% rename from src/main/java/org/cryptomator/ui/common/ErrorComponent.java rename to src/main/java/org/cryptomator/ui/error/ErrorComponent.java index 8cb430584..554aa65f1 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorComponent.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorComponent.java @@ -1,8 +1,10 @@ -package org.cryptomator.ui.common; +package org.cryptomator.ui.error; import dagger.BindsInstance; import dagger.Subcomponent; import org.cryptomator.common.Nullable; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; import javafx.scene.Scene; import javafx.stage.Stage; diff --git a/src/main/java/org/cryptomator/ui/common/ErrorController.java b/src/main/java/org/cryptomator/ui/error/ErrorController.java similarity index 98% rename from src/main/java/org/cryptomator/ui/common/ErrorController.java rename to src/main/java/org/cryptomator/ui/error/ErrorController.java index 15d2ee41f..422a44157 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorController.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorController.java @@ -1,8 +1,9 @@ -package org.cryptomator.ui.common; +package org.cryptomator.ui.error; import org.cryptomator.common.Environment; import org.cryptomator.common.ErrorCode; import org.cryptomator.common.Nullable; +import org.cryptomator.ui.common.FxController; import javax.inject.Inject; import javax.inject.Named; diff --git a/src/main/java/org/cryptomator/ui/common/ErrorModule.java b/src/main/java/org/cryptomator/ui/error/ErrorModule.java similarity index 81% rename from src/main/java/org/cryptomator/ui/common/ErrorModule.java rename to src/main/java/org/cryptomator/ui/error/ErrorModule.java index 01b8790c1..d9ac6eab9 100644 --- a/src/main/java/org/cryptomator/ui/common/ErrorModule.java +++ b/src/main/java/org/cryptomator/ui/error/ErrorModule.java @@ -1,10 +1,16 @@ -package org.cryptomator.ui.common; +package org.cryptomator.ui.error; import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.ErrorCode; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxControllerKey; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; +import org.cryptomator.ui.common.FxmlScene; import javax.inject.Named; import javax.inject.Provider; diff --git a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordComponent.java b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordComponent.java similarity index 96% rename from src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordComponent.java rename to src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordComponent.java index e7300bb79..347065b85 100644 --- a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordComponent.java +++ b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordComponent.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.forgetPassword; +package org.cryptomator.ui.forgetpassword; import dagger.BindsInstance; import dagger.Lazy; diff --git a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordController.java similarity index 97% rename from src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java rename to src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordController.java index 8cd43ae22..f8a4fc4dd 100644 --- a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordController.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.forgetPassword; +package org.cryptomator.ui.forgetpassword; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; diff --git a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordModule.java similarity index 98% rename from src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java rename to src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordModule.java index b0c34e9f5..1a06678cb 100644 --- a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java +++ b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordModule.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.forgetPassword; +package org.cryptomator.ui.forgetpassword; import dagger.Binds; import dagger.Module; diff --git a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordScoped.java similarity index 85% rename from src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java rename to src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordScoped.java index 5be483753..bc0bd738e 100644 --- a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java +++ b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordScoped.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.forgetPassword; +package org.cryptomator.ui.forgetpassword; import javax.inject.Scope; import java.lang.annotation.Documented; diff --git a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordWindow.java b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordWindow.java similarity index 85% rename from src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordWindow.java rename to src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordWindow.java index 285119e0a..e9162f45d 100644 --- a/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordWindow.java +++ b/src/main/java/org/cryptomator/ui/forgetpassword/ForgetPasswordWindow.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.forgetPassword; +package org.cryptomator.ui.forgetpassword; import javax.inject.Qualifier; import java.lang.annotation.Documented; diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index cdeb764be..877675b9c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -7,9 +7,7 @@ package org.cryptomator.ui.fxapp; import dagger.Module; import dagger.Provides; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.ui.common.ErrorComponent; -import org.cryptomator.ui.common.StageFactory; +import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; @@ -18,13 +16,9 @@ import org.cryptomator.ui.quit.QuitComponent; import org.cryptomator.ui.traymenu.TrayMenuComponent; import org.cryptomator.ui.unlock.UnlockComponent; -import javax.inject.Named; import javafx.scene.image.Image; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; -import java.util.Collections; -import java.util.List; @Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class}) abstract class FxApplicationModule { diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 5d38a9017..2b4f8e7bc 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -5,7 +5,7 @@ import dagger.Lazy; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.integrations.tray.TrayIntegrationProvider; -import org.cryptomator.ui.common.ErrorComponent; +import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.preferences.PreferencesComponent; diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java index 6f63ee98e..7b8aae875 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java @@ -15,8 +15,6 @@ import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; -import org.cryptomator.ui.common.PasswordStrengthUtil; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; @@ -153,13 +151,6 @@ public abstract class HubKeyLoadingModule { @FxControllerKey(AuthFlowController.class) abstract FxController bindAuthFlowController(AuthFlowController controller); - @Provides - @IntoMap - @FxControllerKey(NewPasswordController.class) - static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) { - return new NewPasswordController(resourceBundle, strengthRater); - } - @Binds @IntoMap @FxControllerKey(InvalidLicenseController.class) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java index d1a3742ae..cc5edfcb4 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java @@ -29,8 +29,8 @@ import java.util.concurrent.ExecutionException; public class HubKeyLoadingStrategy implements KeyLoadingStrategy { private static final String SCHEME_PREFIX = "hub+"; - static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http"; - static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https"; + public static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http"; + public static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https"; private final Stage window; private final KeychainManager keychainManager; diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 9375b0cff..5e4d148cb 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -8,7 +8,7 @@ import dagger.multibindings.StringKey; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.keychain.KeychainAccessException; -import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; +import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java index baadb9a12..6d52362b3 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java @@ -7,7 +7,7 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.WeakBindings; import org.cryptomator.ui.controls.NiceSecurePasswordField; -import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; +import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index 94acba3cc..6fbb8e16e 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -6,7 +6,7 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; -import org.cryptomator.ui.common.ErrorComponent; +import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index cfec43e3e..3986fa01d 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -38,16 +38,11 @@ public interface RecoveryKeyComponent { stage.show(); } - @Subcomponent.Builder - interface Builder { - @BindsInstance - Builder vault(@RecoveryKeyWindow Vault vault); + @Subcomponent.Factory + interface Factory { - @BindsInstance - Builder owner(@Named("keyRecoveryOwner") Stage owner); - - RecoveryKeyComponent build(); + RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java index a1180dd9f..73279396d 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java @@ -81,7 +81,7 @@ public class RecoveryKeyFactory { * @throws IllegalArgumentException If the recoveryKey is invalid * @apiNote This is a long-running operation and should be invoked in a background thread */ - public void resetPasswordWithRecoveryKey(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException { + public void newMasterkeyFileWithPassphrase(Path vaultPath, String recoveryKey, CharSequence newPassword) throws IOException, IllegalArgumentException { final byte[] rawKey = decodeRecoveryKey(recoveryKey); try (var masterkey = new Masterkey(rawKey)) { Path masterkeyPath = vaultPath.resolve(MASTERKEY_FILENAME); diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index a5a0b7ec8..06095eebc 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -13,8 +13,8 @@ import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; -import org.cryptomator.ui.common.PasswordStrengthUtil; +import org.cryptomator.ui.changepassword.NewPasswordController; +import org.cryptomator.ui.changepassword.PasswordStrengthUtil; import org.cryptomator.ui.common.StageFactory; import javax.inject.Named; @@ -140,6 +140,13 @@ abstract class RecoveryKeyModule { @FxControllerKey(RecoveryKeyResetPasswordSuccessController.class) abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller); + @Provides + @IntoMap + @FxControllerKey(RecoveryKeyValidateController.class) + static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) { + return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory); + } + @Provides @IntoMap @FxControllerKey(NewPasswordController.class) diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 9082d7311..944c52043 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -1,14 +1,9 @@ package org.cryptomator.ui.recoverykey; -import com.google.common.base.CharMatcher; -import com.google.common.base.Strings; import dagger.Lazy; import org.cryptomator.common.Nullable; -import org.cryptomator.common.ObservableUtil; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; -import org.cryptomator.cryptofs.VaultConfigLoadException; -import org.cryptomator.cryptofs.VaultKeyInvalidException; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -16,96 +11,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.Observable; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.Scene; -import javafx.scene.control.TextArea; -import javafx.scene.control.TextFormatter; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; import javafx.stage.Stage; -import java.util.Optional; import java.util.ResourceBundle; @RecoveryKeyScoped public class RecoveryKeyRecoverController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); - private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' ')); private final Stage window; - private final Vault vault; - private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; - private final StringProperty recoveryKey; - private final ObservableValue recoveryKeyCorrect; - private final ObservableValue recoveryKeyWrong; - private final ObservableValue recoveryKeyInvalid; - private final RecoveryKeyFactory recoveryKeyFactory; - private final ObjectProperty recoveryKeyState; private final Lazy resetPasswordScene; - private final AutoCompleter autoCompleter; - private volatile boolean isWrongKey; - - public TextArea textarea; + @FXML + RecoveryKeyValidateController recoveryKeyValidateController; @Inject - public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, ResourceBundle resourceBundle) { + public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, ResourceBundle resourceBundle) { this.window = window; window.setTitle(resourceBundle.getString("recoveryKey.recover.title")); - this.vault = vault; - this.unverifiedVaultConfig = unverifiedVaultConfig; - this.recoveryKey = recoveryKey; - this.recoveryKeyFactory = recoveryKeyFactory; this.resetPasswordScene = resetPasswordScene; - this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary()); - this.recoveryKeyState = new SimpleObjectProperty<>(); - this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false); - this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false); - this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false); } @FXML public void initialize() { - recoveryKey.bind(textarea.textProperty()); - textarea.textProperty().addListener(((observable, oldValue, newValue) -> validateRecoveryKey())); - } - - private TextFormatter.Change filterTextChange(TextFormatter.Change change) { - if (Strings.isNullOrEmpty(change.getText())) { - // pass-through caret/selection changes that don't affect the text - return change; - } - if (!ALLOWED_CHARS.matchesAllOf(change.getText())) { - return null; // reject change - } - - String text = change.getControlNewText(); - int caretPos = change.getCaretPosition(); - if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word? - int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0); - String currentWord = text.substring(beginOfWord, caretPos); - Optional suggestion = autoCompleter.autocomplete(currentWord); - if (suggestion.isPresent()) { - String completion = suggestion.get().substring(currentWord.length()); - change.setText(change.getText() + completion); - change.setAnchor(caretPos + completion.length()); - } - } - return change; - } - - @FXML - public void onKeyPressed(KeyEvent keyEvent) { - if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) { - // apply autocompletion: - int pos = textarea.getAnchor(); - textarea.insertText(pos, " "); - textarea.positionCaret(pos + 1); - } } @FXML @@ -118,85 +51,10 @@ public class RecoveryKeyRecoverController implements FxController { window.setScene(resetPasswordScene.get()); } - /** - * Checks, if vault config is signed with the given key. - * - * @param key byte array of possible signing key - * @return true, if vault config is signed with this key - */ - private boolean checkKeyAgainstVaultConfig(byte[] key) { - try { - var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion()); - LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId()); - return true; - } catch (VaultKeyInvalidException e) { - LOG.debug("Provided recovery key does not match vault config signature."); - isWrongKey = true; - return false; - } catch (VaultConfigLoadException e) { - LOG.error("Failed to parse vault config", e); - return false; - } - } - - private void validateRecoveryKey() { - isWrongKey = false; - var 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() - recoveryKeyState.set(RecoveryKeyState.WRONG); - } else { - recoveryKeyState.set(RecoveryKeyState.INVALID); - } - } - /* Getter/Setter */ - public Vault getVault() { - return vault; + public RecoveryKeyValidateController getValidateController() { + return recoveryKeyValidateController; } - public TextFormatter getRecoveryKeyTextFormatter() { - return new TextFormatter<>(this::filterTextChange); - } - - public ObservableValue recoveryKeyInvalidProperty() { - return recoveryKeyInvalid; - } - - public boolean isRecoveryKeyInvalid() { - return recoveryKeyInvalid.getValue(); - } - - public ObservableValue recoveryKeyCorrectProperty() { - return recoveryKeyCorrect; - } - - public boolean isRecoveryKeyCorrect() { - return recoveryKeyCorrect.getValue(); - } - - public ObservableValue recoveryKeyWrongProperty() { - return recoveryKeyWrong; - } - - public boolean isRecoveryKeyWrong() { - return recoveryKeyWrong.getValue(); - } - - private enum RecoveryKeyState { - /** - * Recovery key is a valid key and belongs to this vault - */ - CORRECT, - /** - * Recovery key is a valid key, but does not belong to this vault - */ - WRONG, - /** - * Recovery key is not a valid key. - */ - INVALID; - } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 69e1373e1..18a952ea5 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -5,7 +5,7 @@ import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.NewPasswordController; +import org.cryptomator.ui.changepassword.NewPasswordController; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +76,7 @@ public class RecoveryKeyResetPasswordController implements FxController { @Override protected Void call() throws IOException, IllegalArgumentException { - recoveryKeyFactory.resetPasswordWithRecoveryKey(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters()); + recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters()); return null; } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java new file mode 100644 index 000000000..4a8224ffe --- /dev/null +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java @@ -0,0 +1,180 @@ +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.vaults.Vault; +import org.cryptomator.cryptofs.VaultConfig; +import org.cryptomator.cryptofs.VaultConfigLoadException; +import org.cryptomator.cryptofs.VaultKeyInvalidException; +import org.cryptomator.ui.common.FxController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextFormatter; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; + +public class RecoveryKeyValidateController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); + private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' ')); + + private final Vault vault; + private final VaultConfig.UnverifiedVaultConfig unverifiedVaultConfig; + private final StringProperty recoveryKey; + private final ObservableValue recoveryKeyCorrect; + private final ObservableValue recoveryKeyWrong; + private final ObservableValue recoveryKeyInvalid; + private final RecoveryKeyFactory recoveryKeyFactory; + private final ObjectProperty recoveryKeyState; + private final AutoCompleter autoCompleter; + + private volatile boolean isWrongKey; + + public TextArea textarea; + + public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) { + this.vault = vault; + this.unverifiedVaultConfig = vaultConfig; + this.recoveryKey = recoveryKey; + this.recoveryKeyFactory = recoveryKeyFactory; + this.autoCompleter = new AutoCompleter(recoveryKeyFactory.getDictionary()); + this.recoveryKeyState = new SimpleObjectProperty<>(); + this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false); + this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false); + this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false); + } + + @FXML + public void initialize() { + recoveryKey.bind(textarea.textProperty()); + textarea.textProperty().addListener(((observable, oldValue, newValue) -> validateRecoveryKey())); + } + + private TextFormatter.Change filterTextChange(TextFormatter.Change change) { + if (Strings.isNullOrEmpty(change.getText())) { + // pass-through caret/selection changes that don't affect the text + return change; + } + if (!ALLOWED_CHARS.matchesAllOf(change.getText())) { + return null; // reject change + } + + String text = change.getControlNewText(); + int caretPos = change.getCaretPosition(); + if (caretPos == text.length() || text.charAt(caretPos) == ' ') { // are we at the end of a word? + int beginOfWord = Math.max(text.substring(0, caretPos).lastIndexOf(' ') + 1, 0); + String currentWord = text.substring(beginOfWord, caretPos); + var suggestion = autoCompleter.autocomplete(currentWord); + if (suggestion.isPresent()) { + String completion = suggestion.get().substring(currentWord.length()); + change.setText(change.getText() + completion); + change.setAnchor(caretPos + completion.length()); + } + } + return change; + } + + @FXML + public void onKeyPressed(KeyEvent keyEvent) { + if (keyEvent.getCode() == KeyCode.TAB && textarea.getAnchor() > textarea.getCaretPosition()) { + // apply autocompletion: + int pos = textarea.getAnchor(); + textarea.insertText(pos, " "); + textarea.positionCaret(pos + 1); + } + } + + /** + * Checks, if vault config is signed with the given key. + * + * @param key byte array of possible signing key + * @return true, if vault config is signed with this key + */ + private boolean checkKeyAgainstVaultConfig(byte[] key) { + assert unverifiedVaultConfig != null; + try { + var config = unverifiedVaultConfig.verify(key, unverifiedVaultConfig.allegedVaultVersion()); + LOG.info("Provided recovery key matches vault config signature for vault {}", config.getId()); + return true; + } catch (VaultKeyInvalidException e) { + LOG.debug("Provided recovery key does not match vault config signature."); + isWrongKey = true; + return false; + } catch (VaultConfigLoadException e) { + LOG.error("Failed to parse vault config", e); + return false; + } + } + + private void validateRecoveryKey() { + isWrongKey = false; + var 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() + recoveryKeyState.set(RecoveryKeyState.WRONG); + } else { + recoveryKeyState.set(RecoveryKeyState.INVALID); + } + } + + /* Getter/Setter */ + + public Vault getVault() { + return vault; + } + + public TextFormatter getRecoveryKeyTextFormatter() { + return new TextFormatter<>(this::filterTextChange); + } + + public ObservableValue recoveryKeyInvalidProperty() { + return recoveryKeyInvalid; + } + + public boolean isRecoveryKeyInvalid() { + return recoveryKeyInvalid.getValue(); + } + + public ObservableValue recoveryKeyCorrectProperty() { + return recoveryKeyCorrect; + } + + public boolean isRecoveryKeyCorrect() { + return recoveryKeyCorrect.getValue(); + } + + public ObservableValue recoveryKeyWrongProperty() { + return recoveryKeyWrong; + } + + public boolean isRecoveryKeyWrong() { + return recoveryKeyWrong.getValue(); + } + + private enum RecoveryKeyState { + /** + * Recovery key is a valid key and belongs to this vault + */ + CORRECT, + /** + * Recovery key is a valid key, but does not belong to this vault + */ + WRONG, + /** + * Recovery key is not a valid key. + */ + INVALID; + } + +} diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/HubOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/HubOptionsController.java new file mode 100644 index 000000000..b4eede771 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/vaultoptions/HubOptionsController.java @@ -0,0 +1,27 @@ +package org.cryptomator.ui.vaultoptions; + +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.convertvault.ConvertVaultComponent; + +import javax.inject.Inject; +import javafx.stage.Stage; + +public class HubOptionsController implements FxController { + + private final Vault vault; + private final Stage window; + private final ConvertVaultComponent.Factory convertVaultFactory; + + + @Inject + public HubOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ConvertVaultComponent.Factory convertVaultFactory) { + this.vault = vault; + this.window = window; + this.convertVaultFactory = convertVaultFactory; + } + + public void startConversion() { + convertVaultFactory.create(vault,window).showHubToPasswordWindow(); + } +} diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java index 4978335c7..dd003d93d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java @@ -4,7 +4,7 @@ import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.changepassword.ChangePasswordComponent; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; +import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,14 +23,14 @@ public class MasterkeyOptionsController implements FxController { private final Vault vault; private final Stage window; private final ChangePasswordComponent.Builder changePasswordWindow; - private final RecoveryKeyComponent.Builder recoveryKeyWindow; + private final RecoveryKeyComponent.Factory recoveryKeyWindow; private final ForgetPasswordComponent.Builder forgetPasswordWindow; private final KeychainManager keychain; private final ObservableValue passwordSaved; @Inject - MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) { + MasterkeyOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Factory recoveryKeyWindow, ForgetPasswordComponent.Builder forgetPasswordWindow, KeychainManager keychain) { this.vault = vault; this.window = window; this.changePasswordWindow = changePasswordWindow; @@ -51,12 +51,12 @@ public class MasterkeyOptionsController implements FxController { @FXML public void showRecoveryKey() { - recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow(); + recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow(); } @FXML public void showRecoverVaultDialog() { - recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyRecoverWindow(); + recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow(); } @FXML diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java b/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java index 3fc738fb0..f9470af96 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java @@ -21,4 +21,9 @@ public enum SelectedVaultOptionsTab { */ KEY, + /** + * Show hub tab + */ + HUB + } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java index c0023acb8..3abc23e9e 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java @@ -2,6 +2,8 @@ package org.cryptomator.ui.vaultoptions; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy; +import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +27,7 @@ public class VaultOptionsController implements FxController { public Tab generalTab; public Tab mountTab; public Tab keyTab; + public Tab hubTab; @Inject VaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ObjectProperty selectedTabProperty) { @@ -38,9 +41,13 @@ public class VaultOptionsController implements FxController { window.setOnShowing(this::windowWillAppear); selectedTabProperty.addListener(observable -> this.selectChosenTab()); tabPane.getSelectionModel().selectedItemProperty().addListener(observable -> this.selectedTabChanged()); - if(!vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme().equals("masterkeyfile")){ + var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme(); + if(!vaultScheme.equals(MasterkeyFileLoadingStrategy.SCHEME)){ tabPane.getTabs().remove(keyTab); } + if(!(vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS))){ + tabPane.getTabs().remove(hubTab); + } } private void selectChosenTab() { @@ -53,6 +60,7 @@ public class VaultOptionsController implements FxController { case ANY, GENERAL -> generalTab; case MOUNT -> mountTab; case KEY -> keyTab; + case HUB -> hubTab; }; } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java index a9014cc54..59dd5ae77 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java @@ -13,7 +13,8 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; +import org.cryptomator.ui.convertvault.ConvertVaultComponent; +import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; @@ -26,7 +27,7 @@ import javafx.stage.Stage; import java.util.Map; import java.util.ResourceBundle; -@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class, ForgetPasswordComponent.class}) +@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class, ForgetPasswordComponent.class, ConvertVaultComponent.class}) abstract class VaultOptionsModule { @Provides @@ -84,4 +85,9 @@ abstract class VaultOptionsModule { @IntoMap @FxControllerKey(MasterkeyOptionsController.class) abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller); + + @Binds + @IntoMap + @FxControllerKey(HubOptionsController.class) + abstract FxController bindHubOptionsController(HubOptionsController controller); } diff --git a/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml new file mode 100644 index 000000000..7ea190fd4 --- /dev/null +++ b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/convertvault_hubtopassword_start.fxml b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml new file mode 100644 index 000000000..d5c0a5e0b --- /dev/null +++ b/src/main/resources/fxml/convertvault_hubtopassword_start.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + +