From 538b4ecd0b86646370eecd4f8a6aea1610cc3b31 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 14 Jan 2024 13:59:23 +0100 Subject: [PATCH 1/8] added javadoc --- .../java/org/cryptomator/ui/keyloading/hub/HubConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java index f8ec7b854..84cac8ed2 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/HubConfig.java @@ -1,7 +1,6 @@ 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; @@ -19,6 +18,12 @@ public class HubConfig { @Deprecated // use apiBaseUrl + "/devices/" public String devicesResourceUrl; + /** + * Get the URI pointing to the /api/ base resource. + * + * @return /api/ URI + * @apiNote URI is guaranteed to end on / + */ public URI getApiBaseUrl() { if (apiBaseUrl != null) { // make sure to end on "/": From 0ed73e8b412195cfc311e4de4f6b0b3175adb3c2 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 14 Jan 2024 14:02:35 +0100 Subject: [PATCH 2/8] use string templates for building /api/* URIs --- .../keyloading/hub/ReceiveKeyController.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index e0f305fd2..e5edfe73d 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -46,6 +46,7 @@ public class ReceiveKeyController implements FxController { private final Stage window; private final HubConfig hubConfig; + private final String vaultId; private final String deviceId; private final String bearerToken; private final CompletableFuture result; @@ -53,14 +54,15 @@ public class ReceiveKeyController implements FxController { private final Lazy legacyRegisterDeviceScene; private final Lazy unauthorizedScene; private final Lazy accountInitializationScene; - private final URI vaultBaseUri; private final Lazy invalidLicenseScene; private final HttpClient httpClient; + private final StringTemplate.Processor API_BASE = this::resolveRelativeToApiBase; @Inject public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { this.window = window; this.hubConfig = hubConfig; + this.vaultId = vault.getId(); this.deviceId = deviceId; this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.result = result; @@ -68,7 +70,6 @@ public class ReceiveKeyController implements FxController { this.legacyRegisterDeviceScene = legacyRegisterDeviceScene; this.unauthorizedScene = unauthorizedScene; this.accountInitializationScene = accountInitializationScene; - this.vaultBaseUri = getVaultBaseUri(vault); this.invalidLicenseScene = invalidLicenseScene; this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); this.httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).executor(executor).build(); @@ -83,8 +84,8 @@ public class ReceiveKeyController implements FxController { * STEP 1 (Request): GET vault key for this user */ private void requestVaultMasterkey() { - var accessTokenUri = appendPath(vaultBaseUri, "/access-token"); - var request = HttpRequest.newBuilder(accessTokenUri) // + var vaultKeyUri = API_BASE."vaults/\{vaultId}/access-token"; + var request = HttpRequest.newBuilder(vaultKeyUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // .timeout(REQ_TIMEOUT) // @@ -115,8 +116,8 @@ public class ReceiveKeyController implements FxController { * STEP 2 (Request): GET user key for this device */ private void requestUserKey(String encryptedVaultKey) { - var deviceTokenUri = URI.create(hubConfig.getApiBaseUrl() + "/devices/" + deviceId); - var request = HttpRequest.newBuilder(deviceTokenUri) // + var deviceUri = API_BASE."devices/\{deviceId}"; + var request = HttpRequest.newBuilder(deviceUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // .timeout(REQ_TIMEOUT) // @@ -167,7 +168,7 @@ public class ReceiveKeyController implements FxController { */ @Deprecated private void requestLegacyAccessToken() { - var legacyAccessTokenUri = appendPath(vaultBaseUri, "/keys/" + deviceId); + var legacyAccessTokenUri = API_BASE."vaults/\{vaultId}/keys/\{deviceId}"; var request = HttpRequest.newBuilder(legacyAccessTokenUri) // .header("Authorization", "Bearer " + bearerToken) // .GET() // @@ -249,17 +250,10 @@ public class ReceiveKeyController implements FxController { } } - private static URI getVaultBaseUri(Vault vault) { - try { - var url = vault.getVaultConfigCache().get().getKeyId(); - assert url.getScheme().startsWith(SCHEME_PREFIX); - var correctedScheme = url.getScheme().substring(SCHEME_PREFIX.length()); - return new URI(correctedScheme, url.getSchemeSpecificPart(), url.getFragment()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (URISyntaxException e) { - throw new IllegalStateException("URI constructed from params known to be valid", e); - } + private URI resolveRelativeToApiBase(StringTemplate template) { + var path = template.interpolate(); + var relPath = path.startsWith("/") ? path.substring(1) : path; + return hubConfig.getApiBaseUrl().resolve(relPath); } @JsonIgnoreProperties(ignoreUnknown = true) From 6d974c7fcf0cad7d705d35cbef0ba7a7151026a4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 14 Jan 2024 14:58:58 +0100 Subject: [PATCH 3/8] fix incorrect vaultId --- .../ui/keyloading/hub/ReceiveKeyController.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index e5edfe73d..bdd3fa906 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -62,7 +62,7 @@ public class ReceiveKeyController implements FxController { public ReceiveKeyController(@KeyLoading Vault vault, ExecutorService executor, @KeyLoading Stage window, HubConfig hubConfig, @Named("deviceId") String deviceId, @Named("bearerToken") AtomicReference tokenRef, CompletableFuture result, @FxmlScene(FxmlFile.HUB_REGISTER_DEVICE) Lazy registerDeviceScene, @FxmlScene(FxmlFile.HUB_LEGACY_REGISTER_DEVICE) Lazy legacyRegisterDeviceScene, @FxmlScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE) Lazy unauthorizedScene, @FxmlScene(FxmlFile.HUB_REQUIRE_ACCOUNT_INIT) Lazy accountInitializationScene, @FxmlScene(FxmlFile.HUB_INVALID_LICENSE) Lazy invalidLicenseScene) { this.window = window; this.hubConfig = hubConfig; - this.vaultId = vault.getId(); + this.vaultId = extractVaultId(vault.getVaultConfigCache().getUnchecked().getKeyId()); // TODO: access vault config's JTI directly (requires changes in cryptofs) this.deviceId = deviceId; this.bearerToken = Objects.requireNonNull(tokenRef.get()); this.result = result; @@ -256,6 +256,12 @@ public class ReceiveKeyController implements FxController { return hubConfig.getApiBaseUrl().resolve(relPath); } + private static String extractVaultId(URI vaultKeyUri) { + assert vaultKeyUri.getScheme().startsWith(SCHEME_PREFIX); + var path = vaultKeyUri.getPath(); + return path.substring(path.lastIndexOf('/') + 1); + } + @JsonIgnoreProperties(ignoreUnknown = true) private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {} } From 748f895b987993cc247a318254062a59cbf80200 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 14 Jan 2024 15:01:51 +0100 Subject: [PATCH 4/8] change unlock request order --- .../keyloading/hub/ReceiveKeyController.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index bdd3fa906..7171b8f2f 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -3,6 +3,7 @@ package org.cryptomator.ui.keyloading.hub; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; import com.nimbusds.jose.JWEObject; import dagger.Lazy; import org.cryptomator.common.vaults.Vault; @@ -77,13 +78,13 @@ public class ReceiveKeyController implements FxController { @FXML public void initialize() { - requestVaultMasterkey(); + requestDeviceData(); } /** - * STEP 1 (Request): GET vault key for this user + * STEP 2 (Request): GET vault key for this user */ - private void requestVaultMasterkey() { + private void requestVaultMasterkey(String encryptedUserKey) { var vaultKeyUri = API_BASE."vaults/\{vaultId}/access-token"; var request = HttpRequest.newBuilder(vaultKeyUri) // .header("Authorization", "Bearer " + bearerToken) // @@ -91,19 +92,19 @@ public class ReceiveKeyController implements FxController { .timeout(REQ_TIMEOUT) // .build(); httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) // - .thenAcceptAsync(this::receivedVaultMasterkey, Platform::runLater) // + .thenAcceptAsync(response -> receivedVaultMasterkey(encryptedUserKey, response), Platform::runLater) // .exceptionally(this::retrievalFailed); } /** - * STEP 1 (Response): GET vault key for this user + * STEP 2 (Response): GET vault key for this user * * @param response Response */ - private void receivedVaultMasterkey(HttpResponse response) { + private void receivedVaultMasterkey(String encryptedUserKey, HttpResponse response) { LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode()); switch (response.statusCode()) { - case 200 -> requestUserKey(response.body()); + case 200 -> receivedBothEncryptedKeys(response.body(), encryptedUserKey); case 402 -> licenseExceeded(); case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog? case 449 -> accountInitializationRequired(); @@ -113,9 +114,9 @@ public class ReceiveKeyController implements FxController { } /** - * STEP 2 (Request): GET user key for this device + * STEP 1 (Request): GET user key for this device */ - private void requestUserKey(String encryptedVaultKey) { + private void requestDeviceData() { var deviceUri = API_BASE."devices/\{deviceId}"; var request = HttpRequest.newBuilder(deviceUri) // .header("Authorization", "Bearer " + bearerToken) // @@ -123,22 +124,22 @@ public class ReceiveKeyController implements FxController { .timeout(REQ_TIMEOUT) // .build(); httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)) // - .thenAcceptAsync(response -> receivedUserKey(encryptedVaultKey, response), Platform::runLater) // + .thenAcceptAsync(this::receivedDeviceData, Platform::runLater) // .exceptionally(this::retrievalFailed); } /** - * STEP 2 (Response): GET user key for this device + * STEP 1 (Response): GET user key for this device * * @param response Response */ - private void receivedUserKey(String encryptedVaultKey, HttpResponse response) { + private void receivedDeviceData(HttpResponse response) { LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode()); try { switch (response.statusCode()) { case 200 -> { var device = JSON.reader().readValue(response.body(), DeviceDto.class); - receivedBothEncryptedKeys(encryptedVaultKey, device.userPrivateKey); + requestVaultMasterkey(device.userPrivateKey); } case 404 -> needsDeviceRegistration(); // TODO: using the setup code, we can theoretically immediately unlock default -> throw new IllegalStateException("Unexpected response " + response.statusCode()); @@ -152,14 +153,14 @@ public class ReceiveKeyController implements FxController { window.setScene(registerDeviceScene.get()); } - private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) throws IOException { + private void receivedBothEncryptedKeys(String encryptedVaultKey, String encryptedUserKey) { try { var vaultKeyJwe = JWEObject.parse(encryptedVaultKey); var userKeyJwe = JWEObject.parse(encryptedUserKey); result.complete(ReceivedKey.vaultKeyAndUserKey(vaultKeyJwe, userKeyJwe)); window.close(); } catch (ParseException e) { - throw new IOException("Failed to parse JWE", e); + retrievalFailed(e); } } @@ -264,4 +265,7 @@ public class ReceiveKeyController implements FxController { @JsonIgnoreProperties(ignoreUnknown = true) private record DeviceDto(@JsonProperty(value = "userPrivateKey", required = true) String userPrivateKey) {} + + @JsonIgnoreProperties(ignoreUnknown = true) + private record ConfigDto(@JsonProperty(value = "apiLevel") int apiLevel) {} } From b59ce75ecd1b20cdc8dec5e7aed160fa7a70e2e3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 14 Jan 2024 15:02:41 +0100 Subject: [PATCH 5/8] add step 0: check API level --- .../keyloading/hub/ReceiveKeyController.java | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index 7171b8f2f..0604036f9 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -78,7 +78,41 @@ public class ReceiveKeyController implements FxController { @FXML public void initialize() { - requestDeviceData(); + requestApiConfig(); // FIXME: only called once - need to restart after returning from register device + } + + /** + * STEP 0 (Request): GET /api/config + */ + private void requestApiConfig() { + var configUri = API_BASE."config"; + var request = HttpRequest.newBuilder(configUri) // + .GET() // + .timeout(REQ_TIMEOUT) // + .build(); + httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.US_ASCII)) // + .thenAcceptAsync(this::receivedApiConfig, Platform::runLater) // + .exceptionally(this::retrievalFailed); + } + + /** + * STEP 0 (Response): GET /api/config + * + * @param response Response + */ + private void receivedApiConfig(HttpResponse response) { + LOG.debug("GET {} -> Status Code {}", response.request().uri(), response.statusCode()); + Preconditions.checkState(response.statusCode() == 200, "Unexpected response " + response.statusCode()); + try { + var config = JSON.reader().readValue(response.body(), ConfigDto.class); + if (config.apiLevel >= 1) { + requestDeviceData(); + } else { + requestLegacyAccessToken(); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -108,7 +142,6 @@ public class ReceiveKeyController implements FxController { case 402 -> licenseExceeded(); case 403, 410 -> accessNotGranted(); // or vault has been archived, effectively disallowing access - TODO: add specific dialog? case 449 -> accountInitializationRequired(); - case 404 -> requestLegacyAccessToken(); default -> throw new IllegalStateException("Unexpected response " + response.statusCode()); } } From 8e520583738b7055ca42113d6e6dc4d8fe179b84 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 16 Jan 2024 03:58:30 +0100 Subject: [PATCH 6/8] re-attempt receiving key after registering device --- .../keyloading/hub/ReceiveKeyController.java | 6 ++++- .../hub/RegisterSuccessController.java | 25 ++++++++++++++++--- .../resources/fxml/hub_register_success.fxml | 4 +-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java index 0604036f9..eec91b194 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java @@ -78,7 +78,11 @@ public class ReceiveKeyController implements FxController { @FXML public void initialize() { - requestApiConfig(); // FIXME: only called once - need to restart after returning from register device + receiveKey(); + } + + public void receiveKey() { + requestApiConfig(); } /** diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterSuccessController.java b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterSuccessController.java index bba13516c..6988283a3 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterSuccessController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/RegisterSuccessController.java @@ -1,24 +1,43 @@ package org.cryptomator.ui.keyloading.hub; +import dagger.Lazy; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.keyloading.KeyLoading; import javax.inject.Inject; import javafx.fxml.FXML; +import javafx.scene.Scene; import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import java.util.concurrent.CompletableFuture; public class RegisterSuccessController implements FxController { private final Stage window; + private final CompletableFuture result; + private final Lazy receiveKeyScene; + private final ReceiveKeyController receiveKeyController; @Inject - public RegisterSuccessController(@KeyLoading Stage window) { + public RegisterSuccessController(@KeyLoading Stage window, CompletableFuture result, @FxmlScene(FxmlFile.HUB_RECEIVE_KEY) Lazy receiveKeyScene, ReceiveKeyController receiveKeyController) { this.window = window; + this.result = result; + this.receiveKeyScene = receiveKeyScene; + this.receiveKeyController = receiveKeyController; + this.window.addEventHandler(WindowEvent.WINDOW_HIDING, this::windowClosed); } @FXML - public void close() { - window.close(); + public void complete() { + window.setScene(receiveKeyScene.get()); + receiveKeyController.receiveKey(); } + private void windowClosed(WindowEvent windowEvent) { + result.cancel(true); + } + + } diff --git a/src/main/resources/fxml/hub_register_success.fxml b/src/main/resources/fxml/hub_register_success.fxml index 822a4489e..c8309c2f7 100644 --- a/src/main/resources/fxml/hub_register_success.fxml +++ b/src/main/resources/fxml/hub_register_success.fxml @@ -41,9 +41,9 @@