Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusted to Hub 1.3.x API #3041

Merged
merged 28 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
59f5c0c
started new unlock workflow using user-specific private key
overheadhunter May 9, 2023
fe73396
send device type in device registration request
overheadhunter Jun 1, 2023
918ace2
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Jun 14, 2023
5a18d08
register device using a setup code
overheadhunter Jun 22, 2023
448eac8
cleanup
overheadhunter Jun 23, 2023
9fc1efa
adjust labels
overheadhunter Jun 29, 2023
746c8f5
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Jun 30, 2023
f08049b
fix "register device" on legacy hub instances
overheadhunter Jul 4, 2023
61025de
renamed var
overheadhunter Jul 4, 2023
496f9a9
docs
overheadhunter Jul 4, 2023
e358ffd
added tests
overheadhunter Jul 4, 2023
0ad8ce7
handle "invalid setup code" error properly
overheadhunter Jul 4, 2023
8cb21c9
add mark for future improvement
overheadhunter Jul 4, 2023
cd10d38
adjusted to latest API-changes
overheadhunter Jul 6, 2023
6ce34ef
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Jul 28, 2023
f48963a
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Aug 9, 2023
08887f1
Merge branch 'develop' into feature/new-hub-keyloading
tobihagemann Sep 6, 2023
88ad3cd
Merge branch 'develop' into feature/new-hub-keyloading
tobihagemann Sep 20, 2023
f8ff720
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Oct 18, 2023
a1b8bf2
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Oct 18, 2023
86f3cb7
applied suggestions from code review
overheadhunter Oct 18, 2023
1f7ab03
undo JEP 443 changes due to bug in javac
overheadhunter Oct 18, 2023
25e8e81
support `apiBaseUrl` in hub config
overheadhunter Oct 18, 2023
5dedd6b
renamed class again
overheadhunter Oct 18, 2023
34c17be
add request timeouts
overheadhunter Oct 19, 2023
468eed5
cleanup
overheadhunter Oct 19, 2023
88b1c28
Merge branch 'develop' into feature/new-hub-keyloading
overheadhunter Oct 19, 2023
b0dfa22
removed unused class
overheadhunter Oct 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/main/java/org/cryptomator/ui/common/FxmlFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ public enum FxmlFile {
HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
HUB_INVALID_LICENSE("/fxml/hub_invalid_license.fxml"), //
HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //
HUB_REGISTER_DEVICE("/fxml/hub_register_device.fxml"), //
HUB_LEGACY_REGISTER_DEVICE("/fxml/hub_legacy_register_device.fxml"), //
HUB_REGISTER_SUCCESS("/fxml/hub_register_success.fxml"), //
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"),
HUB_REGISTER_FAILED("/fxml/hub_register_failed.fxml"), //
HUB_SETUP_DEVICE("/fxml/hub_setup_device.fxml"), //
HUB_UNAUTHORIZED_DEVICE("/fxml/hub_unauthorized_device.fxml"), //
LOCK_FORCED("/fxml/lock_forced.fxml"), //
LOCK_FAILED("/fxml/lock_failed.fxml"), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ public class AuthFlowController implements FxController {
private final String deviceId;
private final HubConfig hubConfig;
private final AtomicReference<String> tokenRef;
private final CompletableFuture<JWEObject> result;
private final CompletableFuture<ReceivedKey> result;
private final Lazy<Scene> receiveKeyScene;
private final ObjectProperty<URI> authUri;
private AuthFlowTask task;

@Inject
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<JWEObject> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
public AuthFlowController(Application application, @KeyLoading Stage window, ExecutorService executor, @Named("deviceId") String deviceId, HubConfig hubConfig, @Named("bearerToken") AtomicReference<String> tokenRef, CompletableFuture<ReceivedKey> result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy<Scene> receiveKeyScene) {
this.application = application;
this.window = window;
this.executor = executor;
Expand Down

This file was deleted.

19 changes: 0 additions & 19 deletions src/main/java/org/cryptomator/ui/keyloading/hub/HttpHelper.java

This file was deleted.

17 changes: 16 additions & 1 deletion src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.cryptomator.ui.keyloading.hub;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.net.URI;

// needs to be accessible by JSON decoder
@JsonIgnoreProperties(ignoreUnknown = true)
Expand All @@ -9,8 +13,19 @@ public class HubConfig {
public String clientId;
public String authEndpoint;
public String tokenEndpoint;
public String devicesResourceUrl;
public String authSuccessUrl;
public String authErrorUrl;
public @Nullable String apiBaseUrl;
@Deprecated // use apiBaseUrl + "/devices/"
public String devicesResourceUrl;

public URI getApiBaseUrl() {
if (apiBaseUrl != null) {
return URI.create(apiBaseUrl);
} else {
// legacy approach
assert devicesResourceUrl != null;
return URI.create(devicesResourceUrl + "/..").normalize();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.cryptomator.ui.keyloading.hub;

import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.JWEObject;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
Expand Down Expand Up @@ -69,7 +68,7 @@ static AtomicReference<String> provideBearerTokenRef() {

@Provides
@KeyLoadingScoped
static CompletableFuture<JWEObject> provideResult() {
static CompletableFuture<ReceivedKey> provideResult() {
return new CompletableFuture<>();
}

Expand Down Expand Up @@ -114,10 +113,10 @@ static Scene provideHubReceiveKeyScene(@KeyLoading FxmlLoaderFactory fxmlLoaders
}

@Provides
@FxmlScene(FxmlFile.HUB_REGISTER_DEVICE)
@FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_DEVICE);
static Scene provideHubLegacyRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE);
}

@Provides
Expand All @@ -134,6 +133,13 @@ static Scene provideHubRegisterFailedScene(@KeyLoading FxmlLoaderFactory fxmlLoa
return fxmlLoaders.createScene(FxmlFile.HUB_REGISTER_FAILED);
}

@Provides
@FxmlScene(FxmlFile.HUB_SETUP_DEVICE)
@KeyLoadingScoped
static Scene provideHubRegisterDeviceScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.HUB_SETUP_DEVICE);
}

@Provides
@FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE)
@KeyLoadingScoped
Expand Down Expand Up @@ -166,6 +172,11 @@ static Scene provideHubUnauthorizedDeviceScene(@KeyLoading FxmlLoaderFactory fxm
@FxControllerKey(RegisterDeviceController.class)
abstract FxController bindRegisterDeviceController(RegisterDeviceController controller);

@Binds
@IntoMap
@FxControllerKey(LegacyRegisterDeviceController.class)
abstract FxController bindLegacyRegisterDeviceController(LegacyRegisterDeviceController controller);

@Binds
@IntoMap
@FxControllerKey(RegisterSuccessController.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
private final KeychainManager keychainManager;
private final Lazy<Scene> authFlowScene;
private final Lazy<Scene> noKeychainScene;
private final CompletableFuture<JWEObject> result;
private final CompletableFuture<ReceivedKey> result;
private final DeviceKey deviceKey;

@Inject
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, @FxmlScene(FxmlFile.HUB_NO_KEYCHAIN) Lazy<Scene> noKeychainScene, CompletableFuture<ReceivedKey> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
this.window = window;
this.keychainManager = keychainManager;
window.setTitle(windowTitle);
Expand All @@ -60,7 +60,7 @@ public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
var keypair = deviceKey.get();
showWindow(authFlowScene);
var jwe = result.get();
return JWEHelper.decrypt(jwe, keypair.getPrivate());
return jwe.decryptMasterkey(keypair.getPrivate());
} catch (NoKeychainAccessProviderException e) {
showWindow(noKeychainScene);
throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
Expand Down
93 changes: 84 additions & 9 deletions src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,103 @@

import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.ECDHDecrypter;
import com.nimbusds.jose.crypto.ECDHEncrypter;
import com.nimbusds.jose.crypto.PasswordBasedDecrypter;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.gen.JWKGenerator;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.function.Function;

class JWEHelper {

private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key";
private static final String JWE_PAYLOAD_KEY_FIELD = "key";
private static final String EC_ALG = "EC";

private JWEHelper(){}
public static JWEObject encryptUserKey(ECPrivateKey userKey, ECPublicKey deviceKey) {
try {
var encodedUserKey = Base64.getEncoder().encodeToString(userKey.getEncoded());
var keyGen = new ECKeyGenerator(Curve.P_384);
var ephemeralKeyPair = keyGen.generate();
var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build();
var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedUserKey));
var jwe = new JWEObject(header, payload);
jwe.encrypt(new ECDHEncrypter(deviceKey));
return jwe;
} catch (JOSEException e) {
throw new RuntimeException(e);
}
}

public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException {
public static ECPrivateKey decryptUserKey(JWEObject jwe, String setupCode) throws InvalidJweKeyException {
try {
jwe.decrypt(new PasswordBasedDecrypter(setupCode));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}

public static ECPrivateKey decryptUserKey(JWEObject jwe, ECPrivateKey deviceKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(deviceKey));
return decodeUserKey(jwe);
} catch (JOSEException e) {
throw new InvalidJweKeyException(e);
}
}

private static ECPrivateKey decodeUserKey(JWEObject decryptedJwe) {
try {
var keySpec = readKey(decryptedJwe, JWE_PAYLOAD_KEY_FIELD, PKCS8EncodedKeySpec::new);
var factory = KeyFactory.getInstance(EC_ALG);
var privateKey = factory.generatePrivate(keySpec);
if (privateKey instanceof ECPrivateKey ecPrivateKey) {
return ecPrivateKey;
} else {
throw new IllegalStateException(EC_ALG + " key factory not generating ECPrivateKeys");
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(EC_ALG + " not supported");
} catch (InvalidKeySpecException e) {
LOG.warn("Unexpected JWE payload: {}", decryptedJwe.getPayload());
throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
}
}

public static Masterkey decryptVaultKey(JWEObject jwe, ECPrivateKey privateKey) throws InvalidJweKeyException {
try {
jwe.decrypt(new ECDHDecrypter(privateKey));
return readKey(jwe);
return readKey(jwe, JWE_PAYLOAD_KEY_FIELD, Masterkey::new);
} catch (JOSEException e) {
LOG.warn("Failed to decrypt JWE: {}", jwe);
throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e);
throw new InvalidJweKeyException(e);
}
}

private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException {
private static <T> T readKey(JWEObject jwe, String keyField, Function<byte[], T> rawKeyFactory) throws MasterkeyLoadingFailedException {
Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
var fields = jwe.getPayload().toJSONObject();
if (fields == null) {
Expand All @@ -39,11 +107,11 @@ private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedExc
}
var keyBytes = new byte[0];
try {
if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) {
if (fields.get(keyField) instanceof String key) {
keyBytes = BaseEncoding.base64().decode(key);
return new Masterkey(keyBytes);
return rawKeyFactory.apply(keyBytes);
} else {
throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD);
throw new IllegalArgumentException("JWE payload doesn't contain field " + keyField);
}
} catch (IllegalArgumentException e) {
LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
Expand All @@ -52,4 +120,11 @@ private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedExc
Arrays.fill(keyBytes, (byte) 0x00);
}
}

public static class InvalidJweKeyException extends MasterkeyLoadingFailedException {

public InvalidJweKeyException(Throwable cause) {
super("Invalid key", cause);
}
}
}
Loading