derive masterkey from received ECIES params

This commit is contained in:
Sebastian Stenzel
2021-08-09 23:03:36 +02:00
parent 43dbdb3e8f
commit d087a5fdde
7 changed files with 92 additions and 28 deletions

View File

@@ -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();
}

View File

@@ -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));

View File

@@ -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));
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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());

View File

@@ -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() {

View File

@@ -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());