move passphrase entry to subcomponent

and use CompletableFuture instead of UserInteractionLock + AtomicReference
This commit is contained in:
Sebastian Stenzel
2021-12-16 13:56:59 +01:00
parent 0fd6e5bbb0
commit 0bece0f591
10 changed files with 179 additions and 182 deletions

View File

@@ -42,7 +42,7 @@ public enum FxmlFile {
this.ressourcePathString = ressourcePathString;
}
String getRessourcePathString() {
public String getRessourcePathString() {
return ressourcePathString;
}
}

View File

@@ -26,7 +26,7 @@ abstract class KeyLoadingModule {
@Provides
@KeyLoading
@KeyLoadingScoped
static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map<String, Provider<KeyLoadingStrategy>> strategies) {
try {
String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme();
var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme));

View File

@@ -1,62 +0,0 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
class MasterkeyFileLoadingFinisher {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class);
private final Vault vault;
private final Optional<char[]> storedPassword;
private final AtomicReference<char[]> enteredPassword;
private final AtomicBoolean shouldSavePassword;
private final KeychainManager keychain;
@Inject
MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional<char[]> storedPassword, AtomicReference<char[]> enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) {
this.vault = vault;
this.storedPassword = storedPassword;
this.enteredPassword = enteredPassword;
this.shouldSavePassword = shouldSavePassword;
this.keychain = keychain;
}
public void cleanup(boolean successfullyUnlocked) {
if (successfullyUnlocked && shouldSavePassword.get()) {
savePasswordToSystemkeychain();
}
wipePassword(storedPassword.orElse(null));
wipePassword(enteredPassword.getAndSet(null));
}
private void savePasswordToSystemkeychain() {
if (keychain.isSupported()) {
try {
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(enteredPassword.get()));
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}
}
private void wipePassword(char[] pw) {
if (pw != null) {
Arrays.fill(pw, ' ');
}
}
}

View File

@@ -25,30 +25,18 @@ import javax.inject.Named;
import javafx.scene.Scene;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@Module(subcomponents = {ForgetPasswordComponent.class})
@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class})
public abstract class MasterkeyFileLoadingModule {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class);
public enum PasswordEntry {
PASSWORD_ENTERED,
CANCELED
}
public enum MasterkeyFileProvision {
MASTERKEYFILE_PROVIDED,
CANCELED
}
@Provides
@KeyLoadingScoped
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
return new UserInteractionLock<>(null);
}
@Provides
@KeyLoadingScoped
static UserInteractionLock<MasterkeyFileProvision> provideMasterkeyFileProvisionLock() {
@@ -77,26 +65,6 @@ public abstract class MasterkeyFileLoadingModule {
return new AtomicReference<>();
}
@Provides
@KeyLoadingScoped
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicReference<>(storedPassword.orElse(null));
}
@Provides
@Named("savePassword")
@KeyLoadingScoped
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
return new AtomicBoolean(storedPassword.isPresent());
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD)
@KeyLoadingScoped
static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD);
}
@Provides
@FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE)
@KeyLoadingScoped
@@ -104,11 +72,6 @@ public abstract class MasterkeyFileLoadingModule {
return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE);
}
@Binds
@IntoMap
@FxControllerKey(PassphraseEntryController.class)
abstract FxController bindUnlockController(PassphraseEntryController controller);
@Binds
@IntoMap
@FxControllerKey(SelectMasterkeyFileController.class)

View File

@@ -2,12 +2,14 @@ package org.cryptomator.ui.keyloading.masterkeyfile;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.common.BackupHelper;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.integrations.keychain.KeychainAccessException;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
@@ -17,6 +19,7 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -26,6 +29,10 @@ import java.net.URI;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoading
@@ -36,28 +43,28 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
private final Vault vault;
private final MasterkeyFileAccess masterkeyFileAccess;
private final Stage window;
private final Lazy<Scene> passphraseEntryScene;
private final Lazy<Scene> selectMasterkeyFileScene;
private final UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock;
private final PassphraseEntryComponent.Builder passphraseEntry;
private final UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock;
private final AtomicReference<char[]> password;
private final AtomicReference<Path> filePath;
private final MasterkeyFileLoadingFinisher finisher;
private final KeychainManager keychain;
private boolean wrongPassword;
private char[] passphrase;
private boolean savePassphrase;
private boolean wrongPassphrase;
@Inject
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy<Scene> passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, UserInteractionLock<MasterkeyFileLoadingModule.PasswordEntry> passwordEntryLock, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<char[]> password, AtomicReference<Path> filePath, MasterkeyFileLoadingFinisher finisher) {
public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy<Scene> selectMasterkeyFileScene, @Named("savedPassword") Optional<char[]> savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, UserInteractionLock<MasterkeyFileLoadingModule.MasterkeyFileProvision> masterkeyFileProvisionLock, AtomicReference<Path> filePath, KeychainManager keychain) {
this.vault = vault;
this.masterkeyFileAccess = masterkeyFileAccess;
this.window = window;
this.passphraseEntryScene = passphraseEntryScene;
this.selectMasterkeyFileScene = selectMasterkeyFileScene;
this.passwordEntryLock = passwordEntryLock;
this.passphraseEntry = passphraseEntry;
this.masterkeyFileProvisionLock = masterkeyFileProvisionLock;
this.password = password;
this.filePath = filePath;
this.finisher = finisher;
this.keychain = keychain;
this.passphrase = savedPassphrase.orElse(null);
this.savePassphrase = savedPassphrase.isPresent();
}
@Override
@@ -68,8 +75,10 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
if (!Files.exists(filePath)) {
filePath = getAlternateMasterkeyFilePath();
}
CharSequence passphrase = getPassphrase();
var masterkey = masterkeyFileAccess.load(filePath, passphrase);
if (passphrase == null) {
askForPassphrase();
}
var masterkey = masterkeyFileAccess.load(filePath, CharBuffer.wrap(passphrase));
//backup
if (filePath.startsWith(vault.getPath())) {
try {
@@ -90,8 +99,8 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
@Override
public boolean recoverFromException(MasterkeyLoadingFailedException exception) {
if (exception instanceof InvalidPassphraseException) {
this.wrongPassword = true;
password.set(null);
this.wrongPassphrase = true;
this.passphrase = null;
return true; // reattempting key load
} else {
return false; // nothing we can do
@@ -100,7 +109,20 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
@Override
public void cleanup(boolean unlockedSuccessfully) {
finisher.cleanup(unlockedSuccessfully);
if (unlockedSuccessfully && savePassphrase) {
savePasswordToSystemkeychain(passphrase);
}
Arrays.fill(passphrase, '\0');
}
private void savePasswordToSystemkeychain(char[] passphrase) {
if (keychain.isSupported()) {
try {
keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(passphrase));
} catch (KeychainAccessException e) {
LOG.error("Failed to store passphrase in system keychain.", e);
}
}
}
private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException {
@@ -129,21 +151,10 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
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 {
private void askForPassphrase() throws InterruptedException {
var comp = passphraseEntry.savedPassword(passphrase).build();
Platform.runLater(() -> {
window.setScene(passphraseEntryScene.get());
window.setScene(comp.passphraseEntryScene());
window.show();
Window owner = window.getOwner();
if (owner != null) {
@@ -152,11 +163,19 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
} else {
window.centerOnScreen();
}
if (wrongPassword) {
if (wrongPassphrase) {
Animations.createShakeWindowAnimation(window).play();
}
});
return passwordEntryLock.awaitInteraction();
try {
var result = comp.result().get();
this.passphrase = result.passphrase();
this.savePassphrase = result.savePassphrase();
} catch (CancellationException e) {
throw new UnlockCancelledException("Password entry cancelled.");
} catch (ExecutionException e) {
throw new MasterkeyLoadingFailedException("Failed to ask for password.", e);
}
}
}

View File

@@ -0,0 +1,35 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.ui.common.Animations;
import javax.inject.Named;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@PassphraseEntryScoped
@Subcomponent(modules = {PassphraseEntryModule.class})
public interface PassphraseEntryComponent {
@PassphraseEntryScoped
Scene passphraseEntryScene();
@PassphraseEntryScoped
CompletableFuture<PassphraseEntryResult> result();
@Subcomponent.Builder
interface Builder {
@BindsInstance
PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") char[] savedPassword);
PassphraseEntryComponent build();
}
}

View File

@@ -1,16 +1,13 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.common.WeakBindings;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,8 +18,8 @@ import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.BooleanProperty;
@@ -37,33 +34,27 @@ import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.CompletableFuture;
@KeyLoadingScoped
@PassphraseEntryScoped
public class PassphraseEntryController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class);
private final Stage window;
private final Vault vault;
private final AtomicReference<char[]> password;
private final AtomicBoolean savePassword;
private final Optional<char[]> savedPassword;
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
private final CompletableFuture<PassphraseEntryResult> result;
private final char[] savedPassword;
private final ForgetPasswordComponent.Builder forgetPassword;
private final KeychainManager keychain;
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
private final BooleanBinding userInteractionDisabled;
private final BooleanProperty unlockButtonDisabled;
private final StringBinding vaultName;
private final BooleanProperty unlockInProgress = new SimpleBooleanProperty();
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress);
private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty();
/* FXML */
public NiceSecurePasswordField passwordField;
public CheckBox savePasswordCheckbox;
public FontAwesome5IconView unlockInProgressView;
public ImageView face;
public ImageView leftArm;
public ImageView rightArm;
@@ -72,29 +63,25 @@ public class PassphraseEntryController implements FxController {
public Animation unlockAnimation;
@Inject
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<PassphraseEntryResult> result, @Nullable @Named("savedPassword") char[] savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) {
this.window = window;
this.vault = vault;
this.password = password;
this.savePassword = savePassword;
this.result = result;
this.savedPassword = savedPassword;
this.passwordEntryLock = passwordEntryLock;
this.forgetPassword = forgetPassword;
this.keychain = keychain;
this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction());
this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not();
this.unlockButtonDisabled = new SimpleBooleanProperty();
this.vaultName = WeakBindings.bindString(vault.displayNameProperty());
this.window.setOnHiding(this::windowClosed);
window.setOnHiding(this::windowClosed);
result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater);
}
@FXML
public void initialize() {
savePasswordCheckbox.setSelected(savedPassword.isPresent());
if (password.get() != null) {
passwordField.setPassword(password.get());
if (savedPassword != null) {
savePasswordCheckbox.setSelected(true);
passwordField.setPassword(savedPassword);
}
unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty()));
unlockButtonDisabled.bind(unlockInProgress.or(passwordField.textProperty().isEmpty()));
var leftArmTranslation = new Translate(24, 0);
var leftArmRotation = new Rotate(60, 16, 30, 0);
@@ -132,7 +119,7 @@ public class PassphraseEntryController implements FxController {
new KeyFrame(Duration.millis(1000), faceVisible) //
);
passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation());
result.whenCompleteAsync((r, t) -> stopUnlockAnimation());
}
@FXML
@@ -141,26 +128,20 @@ public class PassphraseEntryController implements FxController {
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (passwordEntryLock.awaitingInteraction().get()) {
LOG.debug("Unlock canceled by user.");
passwordEntryLock.interacted(PasswordEntry.CANCELED);
}
LOG.debug("Unlock canceled by user.");
result.cancel(true);
}
@FXML
public void unlock() {
LOG.trace("UnlockController.unlock()");
unlockInProgress.set(true);
CharSequence pwFieldContents = passwordField.getCharacters();
char[] newPw = new char[pwFieldContents.length()];
char[] pw = new char[pwFieldContents.length()];
for (int i = 0; i < pwFieldContents.length(); i++) {
newPw[i] = pwFieldContents.charAt(i);
pw[i] = pwFieldContents.charAt(i);
}
char[] oldPw = password.getAndSet(newPw);
if (oldPw != null) {
Arrays.fill(oldPw, ' ');
}
passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED);
result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected()));
startUnlockAnimation();
}
@@ -184,8 +165,7 @@ public class PassphraseEntryController implements FxController {
@FXML
private void didClickSavePasswordCheckbox() {
savePassword.set(savePasswordCheckbox.isSelected());
if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) {
if (!savePasswordCheckbox.isSelected() && savedPassword != null) {
forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten));
}
}
@@ -205,15 +185,15 @@ public class PassphraseEntryController implements FxController {
}
public ContentDisplay getUnlockButtonContentDisplay() {
return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT;
return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
}
public BooleanBinding userInteractionDisabledProperty() {
return userInteractionDisabled;
public ReadOnlyBooleanProperty userInteractionDisabledProperty() {
return unlockInProgress;
}
public boolean isUserInteractionDisabled() {
return userInteractionDisabled.get();
return unlockInProgress.get();
}
public ReadOnlyBooleanProperty unlockButtonDisabledProperty() {
@@ -227,4 +207,6 @@ public class PassphraseEntryController implements FxController {
public boolean isKeychainAccessAvailable() {
return keychain.isSupported();
}
}

View File

@@ -0,0 +1,41 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
@Module
abstract class PassphraseEntryModule {
@Provides
@PassphraseEntryScoped
static CompletableFuture<PassphraseEntryResult> provideResult() {
return new CompletableFuture<>();
}
@Provides
@PassphraseEntryScoped
static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
// TODO: simplify FxmlLoaderFactory
try {
var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_ENTER_PASSWORD.getRessourcePathString());
var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller);
Parent root = loader.load();
return sceneFactory.apply(root);
} catch (IOException e) {
throw new UncheckedIOException("Failed to load UnlockScene", e);
}
}
}

View File

@@ -0,0 +1,6 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
// TODO needs to be public due to Dagger -.-
public record PassphraseEntryResult(char[] passphrase, boolean savePassphrase) {
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
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 PassphraseEntryScoped {
}