From 69c63702f268214491c5c4d92f508e1c9095af16 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 23 Apr 2021 09:27:58 +0200 Subject: [PATCH] pass a single keyloader to cryptofs --- .../org/cryptomator/common/vaults/Vault.java | 2 +- main/pom.xml | 2 +- .../CreateNewVaultPasswordController.java | 32 +++-- .../StaticMasterkeyFileLoaderContext.java | 28 ---- .../ui/keyloading/KeyLoadingComponent.java | 3 + .../ui/keyloading/KeyLoadingModule.java | 4 +- .../ui/keyloading/KeyLoadingStrategy.java | 35 +++-- .../MasterkeyFileLoadingContext.java | 129 ----------------- .../MasterkeyFileLoadingModule.java | 10 +- .../MasterkeyFileLoadingStrategy.java | 132 ++++++++++++++++-- .../cryptomator/ui/unlock/UnlockWorkflow.java | 2 +- 11 files changed, 172 insertions(+), 207 deletions(-) delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/StaticMasterkeyFileLoaderContext.java delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingContext.java diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java index fdcff7e9c..dfbeabffb 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -123,7 +123,7 @@ public class Vault { } CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() // - .withKeyLoaders(keyLoader) // + .withKeyLoader(keyLoader) // .withFlags(flags) // .withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) // .build(); diff --git a/main/pom.xml b/main/pom.xml index bb92e6f6c..75962b02d 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -25,7 +25,7 @@ 16 - 2.0.0-beta7 + 2.0.0-beta8 1.0.0-beta2 1.0.0-beta2 1.0.0-beta2 diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java index b37ffc80c..b55c0df60 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java @@ -8,13 +8,14 @@ import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptofs.VaultCipherCombo; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.cryptomator.cryptolib.common.MasterkeyFileLoader; import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.Tasks; +import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy; import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +37,7 @@ import javafx.scene.control.ToggleGroup; import javafx.stage.Stage; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URI; import java.nio.channels.WritableByteChannel; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -53,6 +55,7 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME; 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; @@ -174,23 +177,22 @@ public class CreateNewVaultPasswordController implements FxController { Path masterkeyFilePath = path.resolve(MASTERKEY_FILENAME); try (Masterkey masterkey = Masterkey.generate(csprng)) { masterkeyFileAccess.persist(masterkey, masterkeyFilePath, passphrase); - } - // 2. initialize vault: - var context = new StaticMasterkeyFileLoaderContext(masterkeyFilePath, passphrase); - var loader = masterkeyFileAccess.keyLoader(path, context); - try { - CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoaders(loader).build(); - CryptoFileSystemProvider.initialize(path, fsProps, MasterkeyFileLoader.keyId(MASTERKEY_FILENAME)); + // 2. initialize vault: + try { + MasterkeyLoader loader = ignored -> masterkey; + CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoader(loader).build(); + CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID); - // 3. write vault-internal readme file: - String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName"); - try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); // - WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { - ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf())); + // 3. write vault-internal readme file: + String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName"); + try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); // + WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) { + ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf())); + } + } catch (CryptoException e) { + throw new IOException("Failed initialize vault.", e); } - } catch (CryptoException e) { - throw new IOException("Failed initialize vault.", e); } // 4. write vault-external readme file: diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/StaticMasterkeyFileLoaderContext.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/StaticMasterkeyFileLoaderContext.java deleted file mode 100644 index 13c3b05b1..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/StaticMasterkeyFileLoaderContext.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.cryptomator.ui.addvaultwizard; - -import com.google.common.base.Preconditions; -import org.cryptomator.cryptolib.common.MasterkeyFileLoaderContext; - -import java.nio.file.Path; - -class StaticMasterkeyFileLoaderContext implements MasterkeyFileLoaderContext { - - private final Path masterkeyFilePath; - private final CharSequence passphrase; - - StaticMasterkeyFileLoaderContext(Path masterkeyFilePath, CharSequence passphrase) { - this.masterkeyFilePath = masterkeyFilePath; - this.passphrase = passphrase; - } - - @Override - public Path getCorrectMasterkeyFilePath(String s) { - return masterkeyFilePath; - } - - @Override - public CharSequence getPassphrase(Path path) { - Preconditions.checkArgument(masterkeyFilePath.equals(path)); - return passphrase; - } -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingComponent.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingComponent.java index fc274d2a7..190bf0e1f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingComponent.java @@ -3,8 +3,11 @@ package org.cryptomator.ui.keyloading; import dagger.BindsInstance; import dagger.Subcomponent; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptolib.api.MasterkeyLoader; import javafx.stage.Stage; +import java.util.Map; +import java.util.function.Supplier; @KeyLoadingScoped @Subcomponent(modules = {KeyLoadingModule.class}) diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java index 6621c5e92..15a5d27b8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java @@ -35,13 +35,13 @@ abstract class KeyLoadingModule { @Provides @KeyLoading @KeyLoadingScoped - static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Optional keyId, Map strategies) { + static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Optional keyId, Map> strategies) { if (keyId.isEmpty()) { return KeyLoadingStrategy.failed(new IllegalArgumentException("No key id provided")); } else { String scheme = keyId.get().getScheme(); var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme)); - return strategies.getOrDefault(scheme, fallback); + return strategies.getOrDefault(scheme, () -> fallback).get(); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java index 80f65b6bc..ed8ca0540 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java +++ b/main/ui/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingStrategy.java @@ -1,21 +1,34 @@ package org.cryptomator.ui.keyloading; +import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoader; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -public interface KeyLoadingStrategy { +import java.net.URI; + +/** + * A reusable, stateful {@link MasterkeyLoader}, that can deal with certain exceptions. + */ +@FunctionalInterface +public interface KeyLoadingStrategy extends MasterkeyLoader { /** - * @return A reusable masterkey loader, preconfigured with the vault of the current unlock process - * @throws MasterkeyLoadingFailedException If unable to provide the masterkey loader - */ - MasterkeyLoader masterkeyLoader() throws MasterkeyLoadingFailedException; - - /** - * Allows the component to try and recover from an exception thrown while loading a masterkey. + * Loads a master key. This might be a long-running operation, as it may require user input or expensive computations. + *

+ * If loading fails exceptionally, this strategy might be able to {@link #recoverFromException(MasterkeyLoadingFailedException) recover from this exception}, so it can be used in a further attempt. * - * @param exception An exception thrown either by {@link #masterkeyLoader()} or by the returned {@link MasterkeyLoader}. - * @return true if this component was able to handle the exception and another attempt should be made to load a masterkey + * @param keyId An URI uniquely identifying the source and identity of the key + * @return The raw key bytes. Must not be null + * @throws MasterkeyLoadingFailedException Thrown when it is impossible to fulfill the request + */ + @Override + Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException; + + /** + * Allows the loader to try and recover from an exception thrown during the last attempt. + * + * @param exception An exception thrown by {@link #loadKey(URI)}. + * @return true if this component was able to handle the exception and another attempt can be made to load a masterkey */ default boolean recoverFromException(MasterkeyLoadingFailedException exception) { return false; @@ -38,7 +51,7 @@ public interface KeyLoadingStrategy { * @return A new KeyLoadingStrategy that will always fail with an {@link MasterkeyLoadingFailedException}. */ static KeyLoadingStrategy failed(Exception exception) { - return () -> { + return keyid -> { if (exception instanceof MasterkeyLoadingFailedException e) { throw e; } else { diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingContext.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingContext.java deleted file mode 100644 index 7445d581d..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingContext.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.cryptomator.ui.keyloading.masterkeyfile; - -import dagger.Lazy; -import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.cryptomator.cryptolib.common.MasterkeyFileLoaderContext; -import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; -import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.cryptomator.ui.unlock.UnlockCancelledException; - -import javax.inject.Inject; -import javafx.application.Platform; -import javafx.scene.Scene; -import javafx.stage.Stage; -import javafx.stage.Window; -import java.nio.CharBuffer; -import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicReference; - -@KeyLoadingScoped -class MasterkeyFileLoadingContext implements MasterkeyFileLoaderContext { - - private final Stage window; - private final Lazy passphraseEntryScene; - private final Lazy selectMasterkeyFileScene; - private final UserInteractionLock passwordEntryLock; - private final UserInteractionLock masterkeyFileProvisionLock; - private final AtomicReference password; - private final AtomicReference filePath; - - private boolean wrongPassword; - - @Inject - public MasterkeyFileLoadingContext(@KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, UserInteractionLock passwordEntryLock, UserInteractionLock masterkeyFileProvisionLock, AtomicReference password, AtomicReference filePath) { - this.window = window; - this.passphraseEntryScene = passphraseEntryScene; - this.selectMasterkeyFileScene = selectMasterkeyFileScene; - this.passwordEntryLock = passwordEntryLock; - this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; - this.password = password; - this.filePath = filePath; - } - - @Override - public Path getCorrectMasterkeyFilePath(String masterkeyFilePath) { - if (filePath.get() != null) { // e.g. already chosen on previous attempt with wrong password - return filePath.get(); - } - - assert filePath.get() == null; - try { - if (askForCorrectMasterkeyFile() == MasterkeyFileLoadingModule.MasterkeyFileProvision.MASTERKEYFILE_PROVIDED) { - return filePath.get(); - } else { - throw new UnlockCancelledException("Choosing masterkey file cancelled."); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new UnlockCancelledException("Choosing masterkey file interrupted", e); - } - } - - private MasterkeyFileLoadingModule.MasterkeyFileProvision askForCorrectMasterkeyFile() throws InterruptedException { - Platform.runLater(() -> { - window.setScene(selectMasterkeyFileScene.get()); - window.show(); - Window owner = window.getOwner(); - if (owner != null) { - window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2); - window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2); - } else { - window.centerOnScreen(); - } - }); - return masterkeyFileProvisionLock.awaitInteraction(); - } - - @Override - public CharSequence getPassphrase(Path path) throws UnlockCancelledException { - if (password.get() != null) { // e.g. pre-filled from keychain - return CharBuffer.wrap(password.get()); - } - - assert password.get() == null; - try { - if (askForPassphrase() == MasterkeyFileLoadingModule.PasswordEntry.PASSWORD_ENTERED) { - assert password.get() != null; - return CharBuffer.wrap(password.get()); - } else { - throw new UnlockCancelledException("Password entry cancelled."); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new UnlockCancelledException("Password entry interrupted", e); - } - } - - private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException { - Platform.runLater(() -> { - window.setScene(passphraseEntryScene.get()); - window.show(); - Window owner = window.getOwner(); - if (owner != null) { - window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2); - window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2); - } else { - window.centerOnScreen(); - } - if (wrongPassword) { - Animations.createShakeWindowAnimation(window).play(); - } - }); - return passwordEntryLock.awaitInteraction(); - } - - public boolean recoverFromException(MasterkeyLoadingFailedException exception) { - if (exception instanceof InvalidPassphraseException) { - this.wrongPassword = true; - password.set(null); - return true; // reattempting key load - } else { - return false; // nothing we can do - } - } -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 31b123f53..d9413121c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -7,8 +7,6 @@ import dagger.multibindings.IntoMap; import dagger.multibindings.StringKey; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; -import org.cryptomator.cryptolib.common.MasterkeyFileAccess; -import org.cryptomator.cryptolib.common.MasterkeyFileLoader; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -45,12 +43,6 @@ public abstract class MasterkeyFileLoadingModule { CANCELED } - @Provides - @KeyLoadingScoped - static MasterkeyFileLoader provideMasterkeyFileLoader(MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Vault vault, MasterkeyFileLoadingContext context) { - return masterkeyFileAccess.keyLoader(vault.getPath(), context); - } - @Provides @KeyLoadingScoped static UserInteractionLock providePasswordEntryLock() { @@ -125,7 +117,7 @@ public abstract class MasterkeyFileLoadingModule { @Binds @IntoMap @KeyLoadingScoped - @StringKey("masterkeyfile") + @StringKey(MasterkeyFileLoadingStrategy.SCHEME) abstract KeyLoadingStrategy bindMasterkeyFileLoadingStrategy(MasterkeyFileLoadingStrategy strategy); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index ae6694198..464671929 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/main/ui/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -1,38 +1,150 @@ package org.cryptomator.ui.keyloading.masterkeyfile; +import com.google.common.base.Preconditions; +import dagger.Lazy; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; -import org.cryptomator.cryptolib.common.MasterkeyFileLoader; +import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.ui.common.Animations; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; +import org.cryptomator.ui.unlock.UnlockCancelledException; import javax.inject.Inject; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.stage.Window; +import java.net.URI; +import java.nio.CharBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; @KeyLoading -class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { +public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { - private final MasterkeyFileLoader masterkeyFileLoader; - private final MasterkeyFileLoadingContext context; + public static final String SCHEME = "masterkeyfile"; + + private final Vault vault; + private final MasterkeyFileAccess masterkeyFileAcccess; + private final Stage window; + private final Lazy passphraseEntryScene; + private final Lazy selectMasterkeyFileScene; + private final UserInteractionLock passwordEntryLock; + private final UserInteractionLock masterkeyFileProvisionLock; + private final AtomicReference password; + private final AtomicReference filePath; private final MasterkeyFileLoadingFinisher finisher; + private boolean wrongPassword; + @Inject - public MasterkeyFileLoadingStrategy(MasterkeyFileLoader masterkeyFileLoader, MasterkeyFileLoadingContext context, MasterkeyFileLoadingFinisher finisher) { - this.masterkeyFileLoader = masterkeyFileLoader; - this.context = context; + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAcccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, UserInteractionLock passwordEntryLock, UserInteractionLock masterkeyFileProvisionLock, AtomicReference password, AtomicReference filePath, MasterkeyFileLoadingFinisher finisher) { + this.vault = vault; + this.masterkeyFileAcccess = masterkeyFileAcccess; + this.window = window; + this.passphraseEntryScene = passphraseEntryScene; + this.selectMasterkeyFileScene = selectMasterkeyFileScene; + this.passwordEntryLock = passwordEntryLock; + this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; + this.password = password; + this.filePath = filePath; this.finisher = finisher; } @Override - public MasterkeyFileLoader masterkeyLoader() { - return masterkeyFileLoader; + public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException { + Preconditions.checkArgument(SCHEME.equalsIgnoreCase(keyId.getScheme()), "Only supports keys with scheme " + SCHEME); + + try { + Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart()); + if (!Files.exists(filePath)) { + filePath = getAlternateMasterkeyFilePath(); + } + CharSequence passphrase = getPassphrase(); + return masterkeyFileAcccess.load(filePath, passphrase); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new UnlockCancelledException("Unlock interrupted", e); + } } @Override public boolean recoverFromException(MasterkeyLoadingFailedException exception) { - return context.recoverFromException(exception); + if (exception instanceof InvalidPassphraseException) { + this.wrongPassword = true; + password.set(null); + return true; // reattempting key load + } else { + return false; // nothing we can do + } } @Override public void cleanup(boolean unlockedSuccessfully) { finisher.cleanup(unlockedSuccessfully); } + + private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException { + if (filePath == null) { + return switch (askUserForMasterkeyFilePath()) { + case MASTERKEYFILE_PROVIDED -> filePath.get(); + case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled."); + }; + } else { + return filePath.get(); + } + } + + private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException { + Platform.runLater(() -> { + window.setScene(selectMasterkeyFileScene.get()); + window.show(); + Window owner = window.getOwner(); + if (owner != null) { + window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2); + window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2); + } else { + window.centerOnScreen(); + } + }); + return masterkeyFileProvisionLock.awaitInteraction(); + } + + private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException { + if (password.get() == null) { + return switch (askForPassphrase()) { + case PASSWORD_ENTERED -> CharBuffer.wrap(password.get()); + case CANCELED -> throw new UnlockCancelledException("Password entry cancelled."); + }; + } else { + // e.g. pre-filled from keychain or previous unlock attempt + return CharBuffer.wrap(password.get()); + } + } + + private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException { + Platform.runLater(() -> { + window.setScene(passphraseEntryScene.get()); + window.show(); + Window owner = window.getOwner(); + if (owner != null) { + window.setX(owner.getX() + (owner.getWidth() - window.getWidth()) / 2); + window.setY(owner.getY() + (owner.getHeight() - window.getHeight()) / 2); + } else { + window.centerOnScreen(); + } + if (wrongPassword) { + Animations.createShakeWindowAnimation(window).play(); + } + }); + return passwordEntryLock.awaitInteraction(); + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 28ac1ab0b..36c3eacf9 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -70,7 +70,7 @@ public class UnlockWorkflow extends Task { private void attemptUnlock() throws IOException, VolumeException, InvalidMountPointException, CryptoException { boolean success = false; try { - vault.unlock(keyLoadingStrategy.masterkeyLoader()); + vault.unlock(keyLoadingStrategy); success = true; } catch (MasterkeyLoadingFailedException e) { if (keyLoadingStrategy.recoverFromException(e)) {