From d087a5fdde80c6ae5986580eb35c8bc016cefae9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 9 Aug 2021 23:03:36 +0200 Subject: [PATCH] derive masterkey from received ECIES params --- .../ui/keyloading/hub/AuthController.java | 12 ++-- .../ui/keyloading/hub/AuthReceiveTask.java | 4 +- .../ui/keyloading/hub/AuthReceiver.java | 13 +---- .../ui/keyloading/hub/EciesHelper.java | 58 +++++++++++++++++++ .../hub/{AuthParams.java => EciesParams.java} | 3 +- .../keyloading/hub/HubKeyLoadingModule.java | 6 ++ .../keyloading/hub/HubKeyLoadingStrategy.java | 24 ++++++-- 7 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/cryptomator/ui/keyloading/hub/EciesHelper.java rename src/main/java/org/cryptomator/ui/keyloading/hub/{AuthParams.java => EciesParams.java} (93%) 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 0937bfa73..fdc574225 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthController.java @@ -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 authParamsRef; private final UserInteractionLock authFlowLock; private final AtomicReference 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 keyPairRef, UserInteractionLock authFlowLock, AtomicReference hubUriRef, ErrorComponent.Builder errorComponent) { + public AuthController(Application application, ExecutorService executor, @KeyLoading Stage window, AtomicReference keyPairRef, AtomicReference authParamsRef, UserInteractionLock authFlowLock, AtomicReference 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(); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java index 8ae71f81f..bab4d6cf9 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiveTask.java @@ -8,7 +8,7 @@ import javafx.concurrent.Task; import java.net.URI; import java.util.function.Consumer; -class AuthReceiveTask extends Task { +class AuthReceiveTask extends Task { private static final Logger LOG = LoggerFactory.getLogger(AuthReceiveTask.class); @@ -24,7 +24,7 @@ class AuthReceiveTask extends Task { } @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)); 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 254709c46..bb2c5b76a 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/AuthReceiver.java @@ -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 receivedKeys = new LinkedBlockingQueue<>(); + private final BlockingQueue 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)); } } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/EciesHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/EciesHelper.java new file mode 100644 index 000000000..8823a22ff --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/EciesHelper.java @@ -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"); + } + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java b/src/main/java/org/cryptomator/ui/keyloading/hub/EciesParams.java similarity index 93% rename from src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java rename to src/main/java/org/cryptomator/ui/keyloading/hub/EciesParams.java index 08e8557d4..a196c7530 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/EciesParams.java @@ -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()); diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java index 67f4e2fc4..e774c486a 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java @@ -38,6 +38,12 @@ public abstract class HubKeyLoadingModule { return new AtomicReference<>(); } + @Provides + @KeyLoadingScoped + static AtomicReference provideAuthParamsRef() { + return new AtomicReference<>(); + } + @Provides @KeyLoadingScoped static UserInteractionLock provideAuthFlowLock() { 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 c84f887b2..a7f8f41e0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java @@ -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 p12LoadingScene; private final UserInteractionLock userInteraction; private final AtomicReference hubUriRef; + private final AtomicReference keyPairRef; + private final AtomicReference authParamsRef; @Inject - public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy p12LoadingScene, UserInteractionLock userInteraction, AtomicReference hubUriRef) { + public HubKeyLoadingStrategy(@KeyLoading Vault vault, @KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_P12) Lazy p12LoadingScene, UserInteractionLock userInteraction, AtomicReference hubUriRef, AtomicReference keyPairRef, AtomicReference 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());