diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 33d384785..a2da43316 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -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 */ diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index a6c3a72e6..6fc36d48c 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -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"), // diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java new file mode 100644 index 000000000..cd4f8331c --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java @@ -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 authFlowLock; + private final AtomicReference hubUriRef; + private final ErrorComponent.Builder errorComponent; + private final ObjectProperty redirectUriRef; + private final ObjectBinding authUri; + private final StringBinding authUriHost; + private final BooleanBinding ready; + private final AuthReceiveTask receiveTask; + + @Inject + public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference keyPairRef, UserInteractionLock authFlowLock, AtomicReference 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 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(); + } +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java new file mode 100644 index 000000000..d08aa28a1 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java @@ -0,0 +1,12 @@ +package org.cryptomator.ui.keyloading.hub; + +/** + * Parameters required to decrypt the masterkey: + *
    + *
  • m Encrypted Masterkey (Base64-encoded)
  • + *
  • epk Ephemeral Public Key (TODO: PEM-encoded?)
  • + *
+ */ +record AuthParams(String m, String epk) { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java new file mode 100644 index 000000000..8ae71f81f --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java @@ -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 { + + private static final Logger LOG = LoggerFactory.getLogger(AuthReceiveTask.class); + + private final Consumer 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 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(); + } + } +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java index dfb37a2ab..6e5ca5b56 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java @@ -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 receivedKeys = new LinkedBlockingQueue<>(); + private final BlockingQueue 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)); } } } 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 132ef36d0..67f4e2fc4 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java @@ -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 provideP12KeyLoadingLock() { + static UserInteractionLock provideAuthFlowLock() { return new UserInteractionLock<>(null); } + @Provides + @KeyLoadingScoped + static AtomicReference 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); + } 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 38a6643b0..b3830d22d 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java @@ -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 p12LoadingScene; - private final UserInteractionLock p12LoadingLock; - private final AtomicReference keyPairRef; + private final UserInteractionLock userInteraction; + private final AtomicReference hubUriRef; @Inject - public HubKeyLoadingStrategy(Application application, ExecutorService executor, @KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy p12LoadingScene, UserInteractionLock p12LoadingLock, AtomicReference keyPairRef) { - this.application = application; - this.executor = executor; + public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy p12LoadingScene, UserInteractionLock userInteraction, AtomicReference 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(); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java index b25a7b188..7e62c8a0c 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java @@ -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"); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/P12Controller.java b/src/main/java/org/cryptomator/ui/keyloading/hub/P12Controller.java index 0505bde28..84df68749 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/P12Controller.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/P12Controller.java @@ -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 p12LoadingLock; + private final UserInteractionLock userInteraction; @Inject - public P12Controller(@KeyLoading Stage window, Environment env, AtomicReference keyPairRef, UserInteractionLock p12LoadingLock) { + public P12Controller(@KeyLoading Stage window, Environment env, UserInteractionLock 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); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/P12CreateController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/P12CreateController.java index d892d086f..735950d9e 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/P12CreateController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/P12CreateController.java @@ -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 keyPairRef; - private final UserInteractionLock p12LoadingLock; + private final Lazy authScene; + private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty(); private final ObjectBinding unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled); private final BooleanProperty readyToCreate = new SimpleBooleanProperty(); public NewPasswordController newPasswordController; - @Inject - public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference keyPairRef, UserInteractionLock p12LoadingLock) { + public P12CreateController(@KeyLoading Stage window, Environment env, AtomicReference keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy 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(); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/P12LoadController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/P12LoadController.java index bab2992bb..d774aa4c7 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/P12LoadController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/P12LoadController.java @@ -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 keyPairRef; - private final UserInteractionLock p12LoadingLock; + private final Lazy authScene; private final BooleanProperty userInteractionDisabled = new SimpleBooleanProperty(); private final ObjectBinding unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, userInteractionDisabled); public NiceSecurePasswordField passwordField; @Inject - public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference keyPairRef, UserInteractionLock p12LoadingLock) { + public P12LoadController(@KeyLoading Stage window, Environment env, AtomicReference keyPairRef, @FxmlScene(FxmlFile.HUB_AUTH) Lazy 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(); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveEncryptedMasterkeyTask.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveEncryptedMasterkeyTask.java deleted file mode 100644 index 70a08cca6..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveEncryptedMasterkeyTask.java +++ /dev/null @@ -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 { - - private static final Logger LOG = LoggerFactory.getLogger(ReceiveEncryptedMasterkeyTask.class); - - private final Consumer redirectUriConsumer; - - public ReceiveEncryptedMasterkeyTask(Consumer 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); - } - } -} 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 901eacfb9..d71cff54d 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -35,12 +35,12 @@ public abstract class MasterkeyFileLoadingModule { public enum PasswordEntry { PASSWORD_ENTERED, - CANCELED + CANCELLED } public enum MasterkeyFileProvision { MASTERKEYFILE_PROVIDED, - CANCELED + CANCELLED } @Provides diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 39db1cc04..b2725ef7e 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -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 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 f6ce79e51..312118ad4 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java @@ -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); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java index 39be2b36e..cc103784b 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java @@ -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); } } diff --git a/src/main/resources/fxml/hub_auth.fxml b/src/main/resources/fxml/hub_auth.fxml new file mode 100644 index 000000000..f45203d01 --- /dev/null +++ b/src/main/resources/fxml/hub_auth.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +