mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 17:16:53 -04:00
derive masterkey from received ECIES params
This commit is contained in:
@@ -12,10 +12,7 @@ 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;
|
||||
@@ -23,7 +20,6 @@ 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;
|
||||
@@ -40,6 +36,7 @@ public class AuthController implements FxController {
|
||||
private final ExecutorService executor;
|
||||
private final Stage window;
|
||||
private final KeyPair keyPair;
|
||||
private final AtomicReference<EciesParams> authParamsRef;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
@@ -48,11 +45,12 @@ public class AuthController implements FxController {
|
||||
private final AuthReceiveTask receiveTask;
|
||||
|
||||
@Inject
|
||||
public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock, AtomicReference<URI> hubUriRef, ErrorComponent.Builder errorComponent) {
|
||||
public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference<KeyPair> keyPairRef, AtomicReference<EciesParams> authParamsRef, UserInteractionLock<HubKeyLoadingModule.AuthFlow> authFlowLock, AtomicReference<URI> hubUriRef, ErrorComponent.Builder errorComponent) {
|
||||
this.application = application;
|
||||
this.executor = executor;
|
||||
this.window = window;
|
||||
this.keyPair = Objects.requireNonNull(keyPairRef.get());
|
||||
this.authParamsRef = authParamsRef;
|
||||
this.authFlowLock = authFlowLock;
|
||||
this.hubUriRef = hubUriRef;
|
||||
this.errorComponent = errorComponent;
|
||||
@@ -77,9 +75,7 @@ public class AuthController implements FxController {
|
||||
}
|
||||
|
||||
private void receivedKey(WorkerStateEvent workerStateEvent) {
|
||||
var authParams = receiveTask.getValue();
|
||||
LOG.info("Cryptomator Hub login succeeded: {} encrypted with {}", authParams.getEphemeralPublicKey(), keyPair.getPublic());
|
||||
// TODO decrypt and return masterkey
|
||||
authParamsRef.set(Objects.requireNonNull(receiveTask.getValue()));
|
||||
authFlowLock.interacted(HubKeyLoadingModule.AuthFlow.SUCCESS);
|
||||
window.close();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import javafx.concurrent.Task;
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
class AuthReceiveTask extends Task<AuthParams> {
|
||||
class AuthReceiveTask extends Task<EciesParams> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AuthReceiveTask.class);
|
||||
|
||||
@@ -24,7 +24,7 @@ class AuthReceiveTask extends Task<AuthParams> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuthParams call() throws Exception {
|
||||
protected EciesParams call() throws Exception {
|
||||
try (var receiver = AuthReceiver.start()) {
|
||||
var redirectUri = receiver.getRedirectURL();
|
||||
Platform.runLater(() -> redirectUriConsumer.accept(redirectUri));
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
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;
|
||||
@@ -86,7 +79,7 @@ class AuthReceiver implements AutoCloseable {
|
||||
return new AuthReceiver(server, connector, servlet);
|
||||
}
|
||||
|
||||
public AuthParams receive() throws InterruptedException {
|
||||
public EciesParams receive() throws InterruptedException {
|
||||
return servlet.receivedKeys.take();
|
||||
}
|
||||
|
||||
@@ -97,7 +90,7 @@ class AuthReceiver implements AutoCloseable {
|
||||
|
||||
private static class CallbackServlet extends HttpServlet {
|
||||
|
||||
private final BlockingQueue<AuthParams> receivedKeys = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<EciesParams> receivedKeys = new LinkedBlockingQueue<>();
|
||||
|
||||
// TODO change to POST?
|
||||
@Override
|
||||
@@ -120,7 +113,7 @@ class AuthReceiver implements AutoCloseable {
|
||||
// the following line might trigger a server shutdown,
|
||||
// so let's make sure the response is flushed first
|
||||
if (m != null && epk != null) {
|
||||
receivedKeys.add(new AuthParams(m, epk));
|
||||
receivedKeys.add(new EciesParams(m, epk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.cryptomator.ui.keyloading.hub;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.cryptolib.common.AesKeyWrap;
|
||||
import org.cryptomator.cryptolib.common.DestroyableSecretKey;
|
||||
|
||||
import javax.crypto.KeyAgreement;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.util.Arrays;
|
||||
|
||||
class EciesHelper {
|
||||
|
||||
private EciesHelper() {}
|
||||
|
||||
public static Masterkey decryptMasterkey(KeyPair deviceKey, EciesParams eciesParams) throws MasterkeyLoadingFailedException {
|
||||
// TODO: include a KDF between key agreement and KEK to conform to ECIES?
|
||||
try (var kek = ecdh(deviceKey.getPrivate(), eciesParams.getEphemeralPublicKey()); //
|
||||
var rawMasterkey = AesKeyWrap.unwrap(kek, eciesParams.getCiphertext(), "HMAC")) {
|
||||
return new Masterkey(rawMasterkey.getEncoded());
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new MasterkeyLoadingFailedException("Unsuitable KEK to decrypt encrypted masterkey", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static DestroyableSecretKey ecdh(PrivateKey privateKey, PublicKey publicKey) {
|
||||
Preconditions.checkArgument(privateKey instanceof ECPrivateKey, "expected ECPrivateKey");
|
||||
Preconditions.checkArgument(publicKey instanceof ECPublicKey, "expected ECPublicKey");
|
||||
byte[] keyBytes = new byte[0];
|
||||
try {
|
||||
var keyAgreement = createKeyAgreement();
|
||||
keyAgreement.init(privateKey);
|
||||
keyAgreement.doPhase(publicKey, true);
|
||||
keyBytes = keyAgreement.generateSecret();
|
||||
return new DestroyableSecretKey(keyBytes, "AES");
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IllegalArgumentException("Invalid keys", e);
|
||||
} finally {
|
||||
Arrays.fill(keyBytes, (byte) 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyAgreement createKeyAgreement() {
|
||||
try {
|
||||
return KeyAgreement.getInstance("ECDH");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("ECDH not supported");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -19,7 +18,7 @@ import java.security.spec.X509EncodedKeySpec;
|
||||
*
|
||||
* No separate tag required, since we use GCM for encryption.
|
||||
*/
|
||||
record AuthParams(String m, String epk) {
|
||||
record EciesParams(String m, String epk) {
|
||||
|
||||
public byte[] getCiphertext() {
|
||||
return BaseEncoding.base64Url().decode(m());
|
||||
@@ -38,6 +38,12 @@ public abstract class HubKeyLoadingModule {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static AtomicReference<EciesParams> provideAuthParamsRef() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@KeyLoadingScoped
|
||||
static UserInteractionLock<AuthFlow> provideAuthFlowLock() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import dagger.Lazy;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.Masterkey;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.cryptolib.common.Destroyables;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
@@ -34,32 +35,43 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
|
||||
private final Lazy<Scene> p12LoadingScene;
|
||||
private final UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction;
|
||||
private final AtomicReference<URI> hubUriRef;
|
||||
private final AtomicReference<KeyPair> keyPairRef;
|
||||
private final AtomicReference<EciesParams> authParamsRef;
|
||||
|
||||
@Inject
|
||||
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction, AtomicReference<URI> hubUriRef) {
|
||||
public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy<Scene> p12LoadingScene, UserInteractionLock<HubKeyLoadingModule.AuthFlow> userInteraction, AtomicReference<URI> hubUriRef, AtomicReference<KeyPair> keyPairRef, AtomicReference<EciesParams> authParamsRef) {
|
||||
this.vault = vault;
|
||||
this.window = window;
|
||||
this.p12LoadingScene = p12LoadingScene;
|
||||
this.userInteraction = userInteraction;
|
||||
this.hubUriRef = hubUriRef;
|
||||
this.keyPairRef = keyPairRef;
|
||||
this.authParamsRef = authParamsRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
|
||||
hubUriRef.set(getHubUri(keyId));
|
||||
try {
|
||||
switch (auth()) {
|
||||
case SUCCESS -> LOG.debug("TODO success"); // TODO return key
|
||||
//case FAILED -> LOG.error("failed to load keypair");
|
||||
return switch (auth()) {
|
||||
case SUCCESS -> EciesHelper.decryptMasterkey(keyPairRef.get(), authParamsRef.get());
|
||||
case FAILED -> throw new MasterkeyLoadingFailedException("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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(boolean unlockedSuccessfully) {
|
||||
var keyPair = keyPairRef.getAndSet(null);
|
||||
if (keyPair != null) {
|
||||
Destroyables.destroySilently(keyPair.getPrivate());
|
||||
}
|
||||
}
|
||||
|
||||
private URI getHubUri(URI keyId) {
|
||||
Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
|
||||
var hubUriScheme = keyId.getScheme().substring(SCHEME_PREFIX.length());
|
||||
|
||||
Reference in New Issue
Block a user