diff --git a/pom.xml b/pom.xml
index 8fd91628f..2fdd357f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -142,6 +142,16 @@
jetty-server
${jetty.version}
+
+ org.eclipse.jetty
+ jetty-webapp
+ ${jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-servlets
+ ${jetty.version}
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index a2da43316..fae730bd1 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -29,6 +29,8 @@ module org.cryptomator.desktop {
requires org.bouncycastle.pkix;
requires org.apache.commons.lang3;
requires org.eclipse.jetty.server;
+ requires org.eclipse.jetty.webapp;
+ requires org.eclipse.jetty.servlets;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java
index cd4f8331c..0937bfa73 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java
@@ -1,6 +1,7 @@
package org.cryptomator.ui.keyloading.hub;
import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.UserInteractionLock;
@@ -43,8 +44,6 @@ public class AuthController implements FxController {
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;
@@ -58,9 +57,7 @@ public class AuthController implements FxController {
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.ready = redirectUriRef.isNotNull();
this.receiveTask = new AuthReceiveTask(redirectUriRef::set);
this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed);
}
@@ -81,7 +78,7 @@ public class AuthController implements FxController {
private void receivedKey(WorkerStateEvent workerStateEvent) {
var authParams = receiveTask.getValue();
- LOG.info("Cryptomator Hub login succeeded: {} encrypted with {}", authParams, keyPair.getPublic());
+ LOG.info("Cryptomator Hub login succeeded: {} encrypted with {}", authParams.getEphemeralPublicKey(), keyPair.getPublic());
// TODO decrypt and return masterkey
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.SUCCESS);
window.close();
@@ -104,40 +101,25 @@ public class AuthController implements FxController {
@FXML
public void openBrowser() {
- assert getAuthUri() != null;
- application.getHostServices().showDocument(getAuthUri().toString());
+ assert ready.get();
+ var hubUri = Objects.requireNonNull(hubUriRef.get());
+ var redirectUri = Objects.requireNonNull(redirectUriRef.get());
+ var sb = new StringBuilder(hubUri.toString());
+ sb.append("?redirect_uri=").append(URLEncoder.encode(redirectUri.toString(), StandardCharsets.US_ASCII));
+ sb.append("&device_id=").append("desktop-app-3000");
+ sb.append("&device_key=").append(BaseEncoding.base64Url().omitPadding().encode(keyPair.getPublic().getEncoded()));
+ var url = sb.toString();
+ application.getHostServices().showDocument(url);
}
/* Getter/Setter */
- public ObjectBinding authUriProperty() {
- return authUri;
- }
-
- public URI getAuthUri() {
+ public String getHubUriHost() {
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) {
+ if (hubUri == null) {
return null;
} else {
- return authUri.getHost();
+ return hubUri.getHost();
}
}
diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java
index d08aa28a1..08e8557d4 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java
@@ -1,12 +1,44 @@
package org.cryptomator.ui.keyloading.hub;
+import com.google.common.io.BaseEncoding;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
/**
- * Parameters required to decrypt the masterkey:
+ * ECIES parameters required to decrypt the masterkey:
*
- * m Encrypted Masterkey (Base64-encoded)
- * epk Ephemeral Public Key (TODO: PEM-encoded?)
+ * m Encrypted Masterkey (base64url-encoded ciphertext)
+ * epk Ephemeral Public Key (base64url-encoded PKCS8)
*
+ *
+ * No separate tag required, since we use GCM for encryption.
*/
record AuthParams(String m, String epk) {
+ public byte[] getCiphertext() {
+ return BaseEncoding.base64Url().decode(m());
+ }
+
+ public ECPublicKey getEphemeralPublicKey() {
+ try {
+ byte[] keyBytes = BaseEncoding.base64Url().decode(epk());
+ PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
+ if (key instanceof ECPublicKey k) {
+ return k;
+ } else {
+ throw new IllegalArgumentException("Key not an EC public key.");
+ }
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException("Invalid license public key", e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
}
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 6e5ca5b56..254709c46 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java
@@ -5,13 +5,25 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.CrossOriginFilter;
+import javax.servlet.DispatcherType;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.util.EnumSet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -37,13 +49,13 @@ class AuthReceiver implements AutoCloseable {
private final Server server;
private final ServerConnector connector;
- private final Handler handler;
+ private final CallbackServlet servlet;
- private AuthReceiver(Server server, ServerConnector connector, Handler handler) {
+ private AuthReceiver(Server server, ServerConnector connector, CallbackServlet servlet) {
assert server.isRunning();
this.server = server;
this.connector = connector;
- this.handler = handler;
+ this.servlet = servlet;
}
public URI getRedirectURL() {
@@ -55,19 +67,27 @@ class AuthReceiver implements AutoCloseable {
}
public static AuthReceiver start() throws Exception {
- Server server = new Server();
- var handler = new Handler();
+ var server = new Server();
+ var context = new ServletContextHandler();
+
+ var corsFilter = new FilterHolder(new CrossOriginFilter());
+ corsFilter.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*"); // TODO restrict to hub host
+ context.addFilter(corsFilter, "/*", EnumSet.of(DispatcherType.REQUEST));
+
+ var servlet = new CallbackServlet();
+ context.addServlet(new ServletHolder(servlet), "/*");
+
var connector = new ServerConnector(server);
connector.setPort(0);
connector.setHost(LOOPBACK_ADDR);
server.setConnectors(new Connector[]{connector});
- server.setHandler(handler);
+ server.setHandler(context);
server.start();
- return new AuthReceiver(server, connector, handler);
+ return new AuthReceiver(server, connector, servlet);
}
public AuthParams receive() throws InterruptedException {
- return handler.receivedKeys.take();
+ return servlet.receivedKeys.take();
}
@Override
@@ -75,13 +95,13 @@ class AuthReceiver implements AutoCloseable {
server.stop();
}
- private static class Handler extends AbstractHandler {
+ private static class CallbackServlet extends HttpServlet {
private final BlockingQueue receivedKeys = new LinkedBlockingQueue<>();
+ // TODO change to POST?
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest req, HttpServletResponse res) throws IOException {
- baseRequest.setHandled(true);
+ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
var m = req.getParameter("m"); // encrypted masterkey
var epk = req.getParameter("epk"); // ephemeral public key
byte[] response;
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 b3830d22d..c84f887b2 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java
@@ -64,7 +64,7 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
var hubUriScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
try {
- return new URI(hubUriScheme, keyId.getSchemeSpecificPart(), null);
+ return new URI(hubUriScheme, keyId.getSchemeSpecificPart(), keyId.getFragment());
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
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 7e62c8a0c..631bf14fc 100644
--- a/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/P12AccessHelper.java
@@ -23,7 +23,7 @@ import java.security.spec.ECGenParameterSpec;
class P12AccessHelper {
private static final String EC_ALG = "EC";
- private static final String EC_CURVE_NAME = "secp256r1";
+ private static final String EC_CURVE_NAME = "secp256r1"; // TODO switch to secp384r1
private static final String SIGNATURE_ALG = "SHA256withECDSA";
private static final String KEYSTORE_ALIAS_KEY = "key";
private static final String KEYSTORE_ALIAS_CERT = "crt";
diff --git a/src/main/resources/fxml/hub_auth.fxml b/src/main/resources/fxml/hub_auth.fxml
index f45203d01..f854e9014 100644
--- a/src/main/resources/fxml/hub_auth.fxml
+++ b/src/main/resources/fxml/hub_auth.fxml
@@ -28,7 +28,7 @@
-
+