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 @@ - +