浏览代码

prepare local webserver for cross-origin requests

Sebastian Stenzel 3 年之前
父节点
当前提交
43dbdb3e8f

+ 10 - 0
pom.xml

@@ -142,6 +142,16 @@
 			<artifactId>jetty-server</artifactId>
 			<version>${jetty.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-webapp</artifactId>
+			<version>${jetty.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-servlets</artifactId>
+			<version>${jetty.version}</version>
+		</dependency>
 
 		<!-- JWT -->
 		<dependency>

+ 2 - 0
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 */

+ 15 - 33
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<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;
 
@@ -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<URI> 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();
 		}
 	}
 

+ 35 - 3
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:
  * <ul>
- *     <li><code>m</code> Encrypted Masterkey (Base64-encoded)</li>
- *     <li><code>epk</code> Ephemeral Public Key (TODO: PEM-encoded?)</li>
+ *     <li><code>m</code> Encrypted Masterkey (base64url-encoded ciphertext)</li>
+ *     <li><code>epk</code> Ephemeral Public Key (base64url-encoded PKCS8)</li>
  * </ul>
+ *
+ * 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);
+		}
+	}
+
 }

+ 31 - 11
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<AuthParams> 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;

+ 1 - 1
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);
 		}

+ 1 - 1
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";

+ 1 - 1
src/main/resources/fxml/hub_auth.fxml

@@ -28,7 +28,7 @@
 			</ImageView>
 			<TextFlow visible="${controller.ready}" managed="${controller.ready}">
 				<Text text="TODO: please login via " />
-				<Hyperlink styleClass="hyperlink-underline" text="${controller.authUriHost}" onAction="#openBrowser"/>
+				<Hyperlink styleClass="hyperlink-underline" text="${controller.hubUriHost}" onAction="#openBrowser"/>
 			</TextFlow>
 			<FontAwesome5Spinner glyphSize="12" visible="${!controller.ready}" managed="${!controller.ready}"/>
 		</HBox>