ソースを参照

derive masterkey from received ECIES params

Sebastian Stenzel 4 年 前
コミット
d087a5fdde

+ 4 - 8
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<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();
 	}

+ 2 - 2
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<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));

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

+ 58 - 0
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");
+		}
+	}
+
+}

+ 1 - 2
src/main/java/org/cryptomator/ui/keyloading/hub/AuthParams.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());

+ 6 - 0
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<EciesParams> provideAuthParamsRef() {
+		return new AtomicReference<>();
+	}
+
 	@Provides
 	@KeyLoadingScoped
 	static UserInteractionLock<AuthFlow> provideAuthFlowLock() {

+ 18 - 6
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<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());