keep window open while waiting for http callback

This commit is contained in:
Sebastian Stenzel
2021-07-30 16:55:05 +02:00
parent 59eda3159b
commit d938b1c3f7
20 changed files with 357 additions and 142 deletions

View File

@@ -9,23 +9,26 @@ module org.cryptomator.desktop {
requires org.cryptomator.frontend.fuse;
requires org.cryptomator.frontend.webdav;
requires org.cryptomator.integrations.api;
// jdk:
requires java.desktop;
requires java.net.http;
requires javafx.base;
requires javafx.graphics;
requires javafx.controls;
requires javafx.fxml;
requires com.tobiasdiez.easybind;
requires jdk.crypto.ec;
// 3rd party:
requires com.auth0.jwt;
requires com.google.common;
requires com.google.gson;
requires com.nulabinc.zxcvbn;
requires org.slf4j;
requires org.apache.commons.lang3;
requires org.eclipse.jetty.server;
requires com.tobiasdiez.easybind;
requires dagger;
requires com.auth0.jwt;
requires org.slf4j;
requires org.bouncycastle.provider;
requires org.bouncycastle.pkix;
requires org.apache.commons.lang3;
requires org.eclipse.jetty.server;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */

View File

@@ -15,6 +15,7 @@ public enum FxmlFile {
HEALTH_START_FAIL("/fxml/health_start_fail.fxml"), //
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
HUB_P12("/fxml/hub_p12.fxml"), //
HUB_AUTH("/fxml/hub_auth.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //
MAIN_WINDOW("/fxml/main_window.fxml"), //

View File

@@ -0,0 +1,151 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
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 javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.WorkerStateEvent;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class AuthController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(AuthController.class);
private final Application application;
private final ExecutorService executor;
private final Stage window;
private final KeyPair keyPair;
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock;
private final AtomicReference<URI> hubUriRef;
private final ErrorComponent.Builder errorComponent;
private final ObjectProperty<URI> redirectUriRef;
private final ObjectBinding<URI> authUri;
private final StringBinding authUriHost;
private final BooleanBinding ready;
private final AuthReceiveTask receiveTask;
@Inject
public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock, AtomicReference<URI> hubUriRef, ErrorComponent.Builder errorComponent) {
this.application = application;
this.executor = executor;
this.window = window;
this.keyPair = Objects.requireNonNull(keyPairRef.get());
this.authFlowLock = authFlowLock;
this.hubUriRef = hubUriRef;
this.errorComponent = errorComponent;
this.redirectUriRef = new SimpleObjectProperty<>();
this.authUri = Bindings.createObjectBinding(this::getAuthUri, redirectUriRef);
this.authUriHost = Bindings.createStringBinding(this::getAuthUriHost, authUri);
this.ready = authUri.isNotNull();
this.receiveTask = new AuthReceiveTask(redirectUriRef::set);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void initialize() {
Preconditions.checkState(hubUriRef.get() != null);
receiveTask.setOnSucceeded(this::receivedKey);
receiveTask.setOnFailed(this::keyRetrievalFailed);
executor.submit(receiveTask);
}
private void keyRetrievalFailed(WorkerStateEvent workerStateEvent) {
LOG.error("Cryptomator Hub login failed with error", receiveTask.getException());
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.FAILED);
errorComponent.cause(receiveTask.getException()).window(window).build().showErrorScene();
}
private void receivedKey(WorkerStateEvent workerStateEvent) {
var authParams = receiveTask.getValue();
LOG.info("Cryptomator Hub login succeeded: {} encrypted with {}", authParams, keyPair.getPublic());
// TODO decrypt and return masterkey
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.SUCCESS);
window.close();
}
@FXML
public void cancel() {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
// stop server, if it is still running
receiveTask.cancel();
// if not already interacted, mark this workflow as cancelled:
if (authFlowLock.awaitingInteraction().get()) {
LOG.debug("Authorization cancelled by user.");
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
}
}
@FXML
public void openBrowser() {
assert getAuthUri() != null;
application.getHostServices().showDocument(getAuthUri().toString());
}
/* Getter/Setter */
public ObjectBinding<URI> authUriProperty() {
return authUri;
}
public URI getAuthUri() {
var hubUri = hubUriRef.get();
var redirectUri = redirectUriRef.get();
if (hubUri == null || redirectUri == null) {
return null;
}
var redirectParam = "redirect_uri=" + URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII);
try {
return new URI(hubUri.getScheme(), hubUri.getAuthority(), hubUri.getPath(), redirectParam, null);
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
}
public StringBinding authUriHostProperty() {
return authUriHost;
}
public String getAuthUriHost() {
var authUri = getAuthUri();
if (authUri == null) {
return null;
} else {
return authUri.getHost();
}
}
public BooleanBinding readyProperty() {
return ready;
}
public boolean isReady() {
return ready.get();
}
}

View File

@@ -0,0 +1,12 @@
package org.cryptomator.ui.keyloading.hub;
/**
* Parameters required to decrypt the masterkey:
* <ul>
* <li><code>m</code> Encrypted Masterkey (Base64-encoded)</li>
* <li><code>epk</code> Ephemeral Public Key (TODO: PEM-encoded?)</li>
* </ul>
*/
record AuthParams(String m, String epk) {
}

View File

@@ -0,0 +1,35 @@
package org.cryptomator.ui.keyloading.hub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.concurrent.Task;
import java.net.URI;
import java.util.function.Consumer;
class AuthReceiveTask extends Task<AuthParams> {
private static final Logger LOG = LoggerFactory.getLogger(AuthReceiveTask.class);
private final Consumer<URI> redirectUriConsumer;
/**
* Spawns a server and waits for the redirectUri to be called.
*
* @param redirectUriConsumer A callback invoked with the redirectUri, as soon as the server has started
*/
public AuthReceiveTask(Consumer<URI> redirectUriConsumer) {
this.redirectUriConsumer = redirectUriConsumer;
}
@Override
protected AuthParams call() throws Exception {
try (var receiver = AuthReceiver.start()) {
var redirectUri = receiver.getRedirectURL();
Platform.runLater(() -> redirectUriConsumer.accept(redirectUri));
LOG.debug("Waiting for key on {}", redirectUri);
return receiver.receive();
}
}
}

View File

@@ -1,6 +1,5 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.BaseEncoding;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@@ -13,19 +12,8 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TransferQueue;
import java.util.function.Consumer;
/**
* A basic implementation for RFC 8252, Section 7.3:
@@ -41,11 +29,11 @@ class AuthReceiver implements AutoCloseable {
private static final String REDIRECT_SCHEME = "http";
private static final String LOOPBACK_ADDR = "127.0.0.1";
private static final String JSON_200 = """
{"status": "success"}
""";
{"status": "success"}
""";
private static final String JSON_400 = """
{"status": "missing param key"}
""";
{"status": "missing param"}
""";
private final Server server;
private final ServerConnector connector;
@@ -78,7 +66,7 @@ class AuthReceiver implements AutoCloseable {
return new AuthReceiver(server, connector, handler);
}
public String receive() throws InterruptedException {
public AuthParams receive() throws InterruptedException {
return handler.receivedKeys.take();
}
@@ -89,14 +77,15 @@ class AuthReceiver implements AutoCloseable {
private static class Handler extends AbstractHandler {
private final BlockingQueue<String> receivedKeys = new LinkedBlockingQueue<>();
private final BlockingQueue<AuthParams> receivedKeys = new LinkedBlockingQueue<>();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse res) throws IOException {
baseRequest.setHandled(true);
var key = req.getParameter("key");
var m = req.getParameter("m"); // encrypted masterkey
var epk = req.getParameter("epk"); // ephemeral public key
byte[] response;
if (key != null) {
if (m != null && epk != null) {
res.setStatus(HttpServletResponse.SC_OK);
response = JSON_200.getBytes(StandardCharsets.UTF_8);
} else {
@@ -110,8 +99,8 @@ class AuthReceiver implements AutoCloseable {
// the following line might trigger a server shutdown,
// so let's make sure the response is flushed first
if (key != null) {
receivedKeys.add(key);
if (m != null && epk != null) {
receivedKeys.add(new AuthParams(m, epk));
}
}
}

View File

@@ -18,6 +18,7 @@ import org.cryptomator.ui.keyloading.KeyLoadingScoped;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import javafx.scene.Scene;
import java.net.URI;
import java.security.KeyPair;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicReference;
@@ -25,10 +26,10 @@ import java.util.concurrent.atomic.AtomicReference;
@Module
public abstract class HubKeyLoadingModule {
public enum P12KeyLoading {
LOADED,
CREATED,
CANCELED
public enum AuthFlow {
SUCCESS,
FAILED,
CANCELLED
}
@Provides
@@ -39,10 +40,16 @@ public abstract class HubKeyLoadingModule {
@Provides
@KeyLoadingScoped
static UserInteractionLock<P12KeyLoading> provideP12KeyLoadingLock() {
static UserInteractionLock<AuthFlow> provideAuthFlowLock() {
return new UserInteractionLock<>(null);
}
@Provides
@KeyLoadingScoped
static AtomicReference<URI> provideHubUri() {
return new AtomicReference<>();
}
@Binds
@IntoMap
@KeyLoadingScoped
@@ -62,6 +69,13 @@ public abstract class HubKeyLoadingModule {
return fxmlLoaders.createScene(FxmlFile.HUB_P12);
}
@Provides
@FxmlScene(FxmlFile.HUB_AUTH)
@KeyLoadingScoped
static Scene provideHubAuthScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_AUTH);
}
@Binds
@IntoMap
@FxControllerKey(P12Controller.class)
@@ -84,4 +98,9 @@ public abstract class HubKeyLoadingModule {
return new NewPasswordController(resourceBundle, strengthRater);
}
@Binds
@IntoMap
@FxControllerKey(AuthController.class)
abstract FxController bindAuthController(AuthController controller);
}

View File

@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.Masterkey;
@@ -14,17 +13,13 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.unlock.UnlockCancelledException;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoading
@@ -34,55 +29,48 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
static final String SCHEME_HUB_HTTP = SCHEME_PREFIX + "http";
static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
private final Application application;
private final ExecutorService executor;
private final Vault vault;
private final Stage window;
private final Lazy<Scene> p12LoadingScene;
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
private final AtomicReference<KeyPair> keyPairRef;
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
private final AtomicReference<URI> hubUriRef;
@Inject
public HubKeyLoadingStrategy(Application application, ExecutorService executor, @KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock, AtomicReference<KeyPair> keyPairRef) {
this.application = application;
this.executor = executor;
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction, AtomicReference<URI> hubUriRef) {
this.vault = vault;
this.window = window;
this.p12LoadingScene = p12LoadingScene;
this.p12LoadingLock = p12LoadingLock;
this.keyPairRef = keyPairRef;
this.userInteraction = userInteraction;
this.hubUriRef = hubUriRef;
}
@Override
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
hubUriRef.set(getHubUri(keyId));
try {
loadP12();
LOG.info("keypair loaded {}", keyPairRef.get().getPublic());
var task = new ReceiveEncryptedMasterkeyTask(redirectUri -> {
openBrowser(keyId, redirectUri);
});
executor.submit(task);
throw new UnlockCancelledException("not yet implemented"); // TODO
switch (auth()) {
case SUCCESS -> LOG.debug("TODO success"); // TODO return key
//case FAILED -> LOG.error("failed to load keypair");
case CANCELLED -> throw new UnlockCancelledException("User cancelled auth workflow");
}
throw new UnlockCancelledException("not yet implemented"); // TODO remove
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new UnlockCancelledException("Loading interrupted", e);
}
}
private void openBrowser(URI keyId, URI redirectUri) {
private URI getHubUri(URI keyId) {
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
var httpScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
var redirectParam = "redirect_uri="+ URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII);
var hubUriScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
try {
var uri = new URI(httpScheme, keyId.getAuthority(), keyId.getPath(), redirectParam, null);
application.getHostServices().showDocument(uri.toString());
return new URI(hubUriScheme, keyId.getSchemeSpecificPart(), null);
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
}
private HubKeyLoadingModule.P12KeyLoading loadP12() throws InterruptedException {
private HubKeyLoadingModule.AuthFlow auth() throws InterruptedException {
Platform.runLater(() -> {
window.setScene(p12LoadingScene.get());
window.show();
@@ -94,7 +82,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
window.centerOnScreen();
}
});
return p12LoadingLock.awaitInteraction();
return userInteraction.awaitInteraction();
}
}

View File

@@ -93,7 +93,7 @@ class P12AccessHelper {
keyGen.initialize(new ECGenParameterSpec(EC_CURVE_NAME));
return keyGen;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
throw new IllegalStateException("secp256r1 curve not supported");
throw new IllegalStateException(EC_CURVE_NAME + " curve not supported");
}
}

View File

@@ -1,35 +1,17 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import org.cryptomator.common.Environment;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.Destroyables;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.NewPasswordController;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
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 javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.control.ContentDisplay;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
@KeyLoadingScoped
public class P12Controller implements FxController {
@@ -38,21 +20,21 @@ public class P12Controller implements FxController {
private final Stage window;
private final Environment env;
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
@Inject
public P12Controller(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
public P12Controller(@KeyLoading Stage window, Environment env, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction) {
this.window = window;
this.env = env;
this.p12LoadingLock = p12LoadingLock;
this.window.setOnHiding(this::windowClosed);
this.userInteraction = userInteraction;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (p12LoadingLock.awaitingInteraction().get()) {
LOG.debug("P12 loading canceled by user.");
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.CANCELED);
if (userInteraction.awaitingInteraction().get()) {
LOG.debug("P12 loading cancelled by user.");
userInteraction.interacted(HubKeyLoadingModule.AuthFlow.CANCELLED);
}
}

View File

@@ -1,9 +1,12 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.Environment;
import org.cryptomator.cryptolib.common.Destroyables;
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.common.UserInteractionLock;
import org.cryptomator.ui.keyloading.KeyLoading;
@@ -19,8 +22,10 @@ import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.nio.file.Path;
import java.security.KeyPair;
@@ -35,25 +40,27 @@ public class P12CreateController implements FxController {
private final Stage window;
private final Environment env;
private final AtomicReference<KeyPair> keyPairRef;
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
private final Lazy<Scene> authScene;
private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty();
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled);
private final BooleanProperty readyToCreate = new SimpleBooleanProperty();
public NewPasswordController newPasswordController;
@Inject
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy<Scene> authScene) {
this.window = window;
this.env = env;
this.keyPairRef = keyPairRef;
this.p12LoadingLock = p12LoadingLock;
this.authScene = authScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void initialize() {
readyToCreate.bind(newPasswordController.goodPasswordProperty());
newPasswordController.passwordField.requestFocus();
}
@FXML
@@ -61,6 +68,11 @@ public class P12CreateController implements FxController {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
newPasswordController.passwordField.wipe();
newPasswordController.reenterField.wipe();
}
@FXML
public void create() {
Preconditions.checkState(newPasswordController.goodPasswordProperty().get());
@@ -70,15 +82,12 @@ public class P12CreateController implements FxController {
var keyPair = P12AccessHelper.createNew(p12File, pw);
setKeyPair(keyPair);
LOG.debug("Created .p12 file {}", p12File);
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.CREATED);
window.close();
window.setScene(authScene.get());
} catch (IOException e) {
LOG.error("Failed to load .p12 file.", e);
// TODO
} finally {
Arrays.fill(pw, '\0');
newPasswordController.passwordField.wipe();
newPasswordController.reenterField.wipe();
}
}

View File

@@ -1,10 +1,13 @@
package org.cryptomator.ui.keyloading.hub;
import dagger.Lazy;
import org.cryptomator.common.Environment;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.common.Destroyables;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.UserInteractionLock;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.keyloading.KeyLoading;
@@ -20,8 +23,10 @@ import javafx.beans.binding.ObjectExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -37,18 +42,24 @@ public class P12LoadController implements FxController {
private final Stage window;
private final Environment env;
private final AtomicReference<KeyPair> keyPairRef;
private final UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock;
private final Lazy<Scene> authScene;
private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty();
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled);
public NiceSecurePasswordField passwordField;
@Inject
public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.P12KeyLoading> p12LoadingLock) {
public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference<KeyPair> keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy<Scene> authScene) {
this.window = window;
this.env = env;
this.keyPairRef = keyPairRef;
this.p12LoadingLock = p12LoadingLock;
this.authScene = authScene;
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@FXML
public void initialize() {
passwordField.requestFocus();
}
@FXML
@@ -56,6 +67,10 @@ public class P12LoadController implements FxController {
window.close();
}
private void windowClosed(WindowEvent windowEvent) {
passwordField.wipe();
}
@FXML
public void load() {
char[] pw = passwordField.copyChars();
@@ -64,8 +79,7 @@ public class P12LoadController implements FxController {
var keyPair = P12AccessHelper.loadExisting(p12File, pw);
setKeyPair(keyPair);
LOG.debug("Loaded .p12 file {}", p12File);
p12LoadingLock.interacted(HubKeyLoadingModule.P12KeyLoading.LOADED);
window.close();
window.setScene(authScene.get());
} catch (InvalidPassphraseException e) {
LOG.warn("Invalid passphrase entered for .p12 file");
Animations.createShakeWindowAnimation(window).playFromStart();
@@ -75,7 +89,6 @@ public class P12LoadController implements FxController {
// TODO
} finally {
Arrays.fill(pw, '\0');
passwordField.wipe();
}
}

View File

@@ -1,31 +0,0 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.io.BaseEncoding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.concurrent.Task;
import java.net.URI;
import java.util.function.Consumer;
class ReceiveEncryptedMasterkeyTask extends Task<byte[]> {
private static final Logger LOG = LoggerFactory.getLogger(ReceiveEncryptedMasterkeyTask.class);
private final Consumer<URI> redirectUriConsumer;
public ReceiveEncryptedMasterkeyTask(Consumer<URI> redirectUriConsumer) {
this.redirectUriConsumer = redirectUriConsumer;
}
@Override
protected byte[] call() throws Exception {
try (var receiver = AuthReceiver.start()) {
var redirectUri = receiver.getRedirectURL();
LOG.debug("Waiting for key on {}", redirectUri);
redirectUriConsumer.accept(redirectUri);
var token = receiver.receive();
return BaseEncoding.base64Url().decode(token);
}
}
}

View File

@@ -35,12 +35,12 @@ public abstract class MasterkeyFileLoadingModule {
public enum PasswordEntry {
PASSWORD_ENTERED,
CANCELED
CANCELLED
}
public enum MasterkeyFileProvision {
MASTERKEYFILE_PROVIDED,
CANCELED
CANCELLED
}
@Provides

View File

@@ -95,7 +95,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
if (filePath.get() == null) {
return switch (askUserForMasterkeyFilePath()) {
case MASTERKEYFILE_PROVIDED -> filePath.get();
case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
case CANCELLED -> throw new UnlockCancelledException("Choosing masterkey file cancelled.");
};
} else {
return filePath.get();
@@ -121,7 +121,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy {
if (password.get() == null) {
return switch (askForPassphrase()) {
case PASSWORD_ENTERED -> CharBuffer.wrap(password.get());
case CANCELED -> throw new UnlockCancelledException("Password entry cancelled.");
case CANCELLED -> throw new UnlockCancelledException("Password entry cancelled.");
};
} else {
// e.g. pre-filled from keychain or previous unlock attempt

View File

@@ -143,8 +143,8 @@ 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 cancelled by user.");
passwordEntryLock.interacted(PasswordEntry.CANCELLED);
}
}

View File

@@ -45,8 +45,8 @@ public class SelectMasterkeyFileController implements FxController {
private void windowClosed(WindowEvent windowEvent) {
// if not already interacted, mark this workflow as cancelled:
if (masterkeyFileProvisionLock.awaitingInteraction().get()) {
LOG.debug("Unlock canceled by user.");
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED);
LOG.debug("Unlock cancelled by user.");
masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELLED);
}
}