mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-18 02:31:27 +00:00
prepare local webserver for cross-origin requests
This commit is contained in:
10
pom.xml
10
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>
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user