Procházet zdrojové kódy

Merge branch 'develop' into feature/mount-setting-per-vault

Armin Schrenk před 2 roky
rodič
revize
fd5aeaf90a

+ 1 - 1
.github/workflows/appimage.yml

@@ -16,7 +16,7 @@ jobs:
   get-version:
     uses: ./.github/workflows/get-version.yml
     with:
-      version: ${{ github.event.inputs.version }}
+      version: ${{ inputs.version }}
 
   build:
     name: Build AppImage

+ 11 - 10
.github/workflows/debian.yml

@@ -30,11 +30,12 @@ jobs:
     steps:
       - uses: actions/checkout@v3
         with:
-          ref: ${{ github.events.inputs.ref }}
+          ref: ${{ inputs.ref }}
+          fetch-depth: 0
       - id: versions
         name: Get version information
         run: |
-          SEM_VER_STR="${{ github.events.inputs.semver }}"
+          SEM_VER_STR="${{ inputs.semver }}"
           SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
           REVCOUNT=`git rev-list --count HEAD`
           echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT
@@ -91,7 +92,7 @@ jobs:
           cp -r jmods pkgdir
           cp -r dist/linux/common/ pkgdir
           cp target/cryptomator-*.jar pkgdir/mods
-          tar -cJf cryptomator_${{ github.event.inputs.ppaver }}.orig.tar.xz -C pkgdir .
+          tar -cJf cryptomator_${{ inputs.ppaver }}.orig.tar.xz -C pkgdir .
       - name: Patch and rename pkgdir
         run: |
           cp -r dist/linux/debian/ pkgdir
@@ -99,12 +100,12 @@ jobs:
           envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules
           envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog
           find . -name "*.jar" >> pkgdir/debian/source/include-binaries
-          mv pkgdir cryptomator_${{ github.event.inputs.ppaver }}
+          mv pkgdir cryptomator_${{ inputs.ppaver }}
         env:
           SEMVER_STR: ${{ steps.versions.outputs.semVerStr }}
           VERSION_NUM: ${{ steps.versions.outputs.semVerNum }}
           REVISION_NUM: ${{ steps.versions.outputs.revNum }}
-          PPA_VERSION: ${{ github.event.inputs.ppaver }}-0ppa1
+          PPA_VERSION: ${{ inputs.ppaver }}-0ppa1
       - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
         run: |
           echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
@@ -119,7 +120,7 @@ jobs:
         env:
           DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback
           DEBSIGN_KEYID: 615D449FE6E6A235
-        working-directory: cryptomator_${{ github.event.inputs.ppaver }}
+        working-directory: cryptomator_${{ inputs.ppaver }}
       - name: Create detached GPG signatures
         run: |
           gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb
@@ -141,16 +142,16 @@ jobs:
       
       # If ref is a tag, also upload to GitHub Releases:
       - name: Determine tag name
-        if: startsWith(github.events.inputs.ref, 'refs/tags/')
+        if: startsWith(inputs.ref, 'refs/tags/')
         run: |
-          REF=${{ github.events.inputs.ref }}
+          REF=${{ inputs.ref }}
           echo "TAG_NAME=${REF##*/}" >> $GITHUB_ENV
       - name: Publish Debian package on GitHub Releases
-        if: startsWith(github.events.inputs.ref, 'refs/tags/')
+        if: startsWith(inputs.ref, 'refs/tags/')
         uses: softprops/action-gh-release@v1
         with:
           fail_on_unmatched_files: true
-          tag_name: ${{ github.env.TAG_NAME }}
+          tag_name: ${{ inputs.ref }}
           token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
           files: |
             cryptomator_*_amd64.deb

+ 4 - 4
.github/workflows/get-version.yml

@@ -58,13 +58,13 @@ jobs:
           SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'`
           REVCOUNT=`git rev-list --count HEAD`
           TYPE="unknown"
-          if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
+          if [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
             TYPE="stable"
-          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9] ]]; then
+          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-alpha[1-9]+$ ]]; then
             TYPE="alpha"
-          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9] ]]; then
+          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-beta[1-9]+$ ]]; then
             TYPE="beta"
-          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9] ]]; then
+          elif [[ $SEM_VER_STR =~ [0-9]+\.[0-9]+\.[0-9]+-rc[1-9]$ ]]; then
             TYPE="rc"
           fi
           echo "semVerStr=${SEM_VER_STR}" >> $GITHUB_OUTPUT

+ 1 - 1
.github/workflows/mac-dmg.yml

@@ -16,7 +16,7 @@ jobs:
   get-version:
     uses: ./.github/workflows/get-version.yml
     with:
-      version: ${{ github.event.inputs.version }}
+      version: ${{ inputs.version }}
 
   build:
     name: Build Cryptomator.app for ${{ matrix.output-suffix }}

+ 1 - 2
.github/workflows/post-publish.yml

@@ -12,7 +12,6 @@ jobs:
         run: |
           curl -L -H "Accept: application/vnd.github+json" ${{ github.event.release.tarball_url }} --output cryptomator-${{ github.event.release.tag_name }}.tar.gz
       - name: Sign source tarball with key 615D449FE6E6A235
-        if: startsWith(github.ref, 'refs/tags/')
         run: |
           echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
           echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.tar.gz
@@ -35,6 +34,6 @@ jobs:
           SLACK_ICON_EMOJI: ':bot:'
           SLACK_CHANNEL: 'cryptomator-desktop'
           SLACK_TITLE: "Release ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
-          SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|deploy to PPA>."
+          SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/debian.yml|build deb Package>."
           SLACK_FOOTER: false
           MSG_MINIMAL: true

+ 10 - 13
.github/workflows/win-exe.yml

@@ -22,7 +22,7 @@ jobs:
   get-version:
     uses: ./.github/workflows/get-version.yml
     with:
-      version: ${{ github.event.inputs.version }}
+      version: ${{ inputs.version }}
 
   build-msi:
     name: Build .msi Installer
@@ -197,6 +197,15 @@ jobs:
             *.msi
             *.asc
 
+  call-winget-flow:
+    needs: [get-version, build-msi]
+    if: github.event.action == 'published' && needs.get-version.outputs.versionType == 'stable'
+    uses: ./.github/workflows/winget.yml
+    with:
+      releaseTag: ${{ github.event.release.tag_name }}
+    secrets: inherit
+
+
   build-exe:
     name: Build .exe installer
     runs-on: windows-latest
@@ -301,18 +310,6 @@ jobs:
           files: |
             Cryptomator-*.exe
             Cryptomator-*.asc
-      - name: Slack Notification
-        uses: rtCamp/action-slack-notify@v2
-        env:
-          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
-          SLACK_USERNAME: 'Cryptobot'
-          SLACK_ICON: false
-          SLACK_ICON_EMOJI: ':bot:'
-          SLACK_CHANNEL: 'cryptomator-desktop'
-          SLACK_TITLE: "Windows build of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} finished."
-          SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml|deploy to Winget>."
-          SLACK_FOOTER: false
-          MSG_MINIMAL: true
 
   allowlist:
     name: Anti Virus Allowlisting

+ 5 - 0
.github/workflows/winget.yml

@@ -1,6 +1,11 @@
 name: Release to Winget
 
 on:
+  workflow_call:
+    inputs:
+      releaseTag:
+        required: true
+        type: string
   workflow_dispatch:
     inputs:
       releaseTag:

+ 1 - 0
dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml

@@ -66,6 +66,7 @@
 	</content_rating>
 
 	<releases>
+		<release date="2022-12-14" version="1.6.17"/>
 		<release date="2022-12-06" version="1.6.16"/>
 		<release date="2022-10-06" version="1.6.15"/>
 		<release date="2022-08-31" version="1.6.14"/>

+ 1 - 1
dist/linux/debian/control

@@ -12,7 +12,7 @@ Package: cryptomator
 Architecture: any
 Section: utils
 Priority: optional
-Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse2, xdg-utils, libjffi-jni
+Depends: ${shlibs:Depends}, ${misc:Depends}, libfuse2, xdg-utils, libjffi-jni, libffi7
 Recommends: gvfs-backends, gvfs-fuse, gnome-keyring
 XB-AppName: Cryptomator
 XB-Category: Utility;Security;FileTools;

+ 1 - 1
dist/linux/debian/cryptomator.sh

@@ -1,6 +1,6 @@
 #!/bin/sh
 
 # fix for https://github.com/cryptomator/cryptomator/issues/1370
-export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so
+export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/jni/libjffi-1.2.so:/usr/lib/x86_64-linux-gnu/libffi.so.7.1.0
 
 /usr/lib/cryptomator/bin/cryptomator $@

+ 8 - 1
pom.xml

@@ -28,8 +28,9 @@
 		<nonModularGroupIds>com.github.jnr,org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh</nonModularGroupIds>
 
 		<!-- cryptomator dependencies -->
+		<cryptomator.cryptolib.version>2.1.1</cryptomator.cryptolib.version>
 		<cryptomator.cryptofs.version>2.5.3</cryptomator.cryptofs.version>
-		<cryptomator.integrations.version>1.2.0-beta2</cryptomator.integrations.version>
+		<cryptomator.integrations.version>1.2.0-beta3</cryptomator.integrations.version>
 		<cryptomator.integrations.win.version>1.1.2</cryptomator.integrations.win.version>
 		<cryptomator.integrations.mac.version>1.1.2</cryptomator.integrations.mac.version>
 		<cryptomator.integrations.linux.version>1.1.0</cryptomator.integrations.linux.version>
@@ -64,6 +65,12 @@
 
 	<dependencies>
 		<!-- Cryptomator Libs -->
+		<dependency>
+			<!-- needed due to https://github.com/cryptomator/cryptolib/issues/34-->
+			<groupId>org.cryptomator</groupId>
+			<artifactId>cryptolib</artifactId>
+			<version>${cryptomator.cryptolib.version}</version>
+		</dependency>
 		<dependency>
 			<groupId>org.cryptomator</groupId>
 			<artifactId>cryptofs</artifactId>

+ 5 - 0
src/main/java/org/cryptomator/common/settings/DeviceKey.java

@@ -76,6 +76,11 @@ public class DeviceKey {
 
 	private P384KeyPair createAndStoreNewKeyPair(char[] passphrase, Path p12File) throws IOException {
 		var keyPair = P384KeyPair.generate();
+		var tmpFile = p12File.resolveSibling(p12File.getFileName().toString() + ".tmp");
+		if(Files.exists(tmpFile)) {
+			LOG.debug("Leftover from devicekey creation detected. Deleting {}", tmpFile);
+			Files.delete(tmpFile);
+		}
 		LOG.debug("Store new device key to {}", p12File);
 		keyPair.store(p12File, passphrase);
 		return keyPair;

+ 1 - 1
src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java

@@ -176,7 +176,7 @@ public class CreateNewVaultPasswordController implements FxController {
 			// 2. initialize vault:
 			try {
 				MasterkeyLoader loader = ignored -> masterkey.copy();
-				CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).withKeyLoader(loader).build();
+				CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_GCM).withKeyLoader(loader).build();
 				CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
 
 				// 3. write vault-internal readme file:

+ 1 - 0
src/main/java/org/cryptomator/ui/common/FxmlFile.java

@@ -13,6 +13,7 @@ public enum FxmlFile {
 	FORGET_PASSWORD("/fxml/forget_password.fxml"), //
 	HEALTH_START("/fxml/health_start.fxml"), //
 	HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
+	HUB_NO_KEYCHAIN("/fxml/hub_no_keychain.fxml"), //
 	HUB_AUTH_FLOW("/fxml/hub_auth_flow.fxml"), //
 	HUB_LICENSE_EXCEEDED("/fxml/hub_license_exceeded.fxml"), //
 	HUB_RECEIVE_KEY("/fxml/hub_receive_key.fxml"), //

+ 12 - 0
src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingModule.java

@@ -87,6 +87,13 @@ public abstract class HubKeyLoadingModule {
 	@StringKey(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS)
 	abstract KeyLoadingStrategy bindHubKeyLoadingStrategyToHubHttps(HubKeyLoadingStrategy strategy);
 
+	@Provides
+	@FxmlScene(FxmlFile.HUB_NO_KEYCHAIN)
+	@KeyLoadingScoped
+	static Scene provideHubNoKeychainScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) {
+		return fxmlLoaders.createScene(FxmlFile.HUB_NO_KEYCHAIN);
+	}
+
 	@Provides
 	@FxmlScene(FxmlFile.HUB_AUTH_FLOW)
 	@KeyLoadingScoped
@@ -136,6 +143,11 @@ public abstract class HubKeyLoadingModule {
 		return fxmlLoaders.createScene(FxmlFile.HUB_UNAUTHORIZED_DEVICE);
 	}
 
+	@Binds
+	@IntoMap
+	@FxControllerKey(NoKeychainController.class)
+	abstract FxController bindNoKeychainController(NoKeychainController controller);
+
 	@Binds
 	@IntoMap
 	@FxControllerKey(AuthFlowController.class)

+ 18 - 5
src/main/java/org/cryptomator/ui/keyloading/hub/HubKeyLoadingStrategy.java

@@ -3,6 +3,8 @@ package org.cryptomator.ui.keyloading.hub;
 import com.google.common.base.Preconditions;
 import com.nimbusds.jose.JWEObject;
 import dagger.Lazy;
+import org.cryptomator.common.keychain.KeychainManager;
+import org.cryptomator.common.keychain.NoKeychainAccessProviderException;
 import org.cryptomator.common.settings.DeviceKey;
 import org.cryptomator.cryptolib.api.Masterkey;
 import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
@@ -31,15 +33,19 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
 	static final String SCHEME_HUB_HTTPS = SCHEME_PREFIX + "https";
 
 	private final Stage window;
+	private final KeychainManager keychainManager;
 	private final Lazy<Scene> authFlowScene;
+	private final Lazy<Scene> noKeychainScene;
 	private final CompletableFuture<JWEObject> result;
 	private final DeviceKey deviceKey;
 
 	@Inject
-	public HubKeyLoadingStrategy(@KeyLoading Stage window, @FxmlScene(FxmlFile.HUB_AUTH_FLOW) Lazy<Scene> authFlowScene, CompletableFuture<JWEObject> result, DeviceKey deviceKey, @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<JWEObject> result, DeviceKey deviceKey, KeychainManager keychainManager, @Named("windowTitle") String windowTitle) {
 		this.window = window;
+		this.keychainManager = keychainManager;
 		window.setTitle(windowTitle);
 		this.authFlowScene = authFlowScene;
+		this.noKeychainScene = noKeychainScene;
 		this.result = result;
 		this.deviceKey = deviceKey;
 	}
@@ -48,9 +54,16 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
 	public Masterkey loadKey(URI keyId) throws MasterkeyLoadingFailedException {
 		Preconditions.checkArgument(keyId.getScheme().startsWith(SCHEME_PREFIX));
 		try {
-			startAuthFlow();
+			if (!keychainManager.isSupported()) {
+				throw new NoKeychainAccessProviderException();
+			}
+			var keypair = deviceKey.get();
+			showWindow(authFlowScene);
 			var jwe = result.get();
-			return JWEHelper.decrypt(jwe, deviceKey.get().getPrivate());
+			return JWEHelper.decrypt(jwe, keypair.getPrivate());
+		} catch (NoKeychainAccessProviderException e) {
+			showWindow(noKeychainScene);
+			throw new UnlockCancelledException("Unlock canceled due to missing prerequisites", e);
 		} catch (DeviceKey.DeviceKeyRetrievalException e) {
 			throw new MasterkeyLoadingFailedException("Failed to load keypair", e);
 		} catch (CancellationException e) {
@@ -63,9 +76,9 @@ public class HubKeyLoadingStrategy implements KeyLoadingStrategy {
 		}
 	}
 
-	private void startAuthFlow() {
+	private void showWindow(Lazy<Scene> scene) {
 		Platform.runLater(() -> {
-			window.setScene(authFlowScene.get());
+			window.setScene(scene.get());
 			window.show();
 			Window owner = window.getOwner();
 			if (owner != null) {

+ 31 - 0
src/main/java/org/cryptomator/ui/keyloading/hub/NoKeychainController.java

@@ -0,0 +1,31 @@
+package org.cryptomator.ui.keyloading.hub;
+
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.FxApplicationWindows;
+import org.cryptomator.ui.keyloading.KeyLoading;
+import org.cryptomator.ui.preferences.SelectedPreferencesTab;
+
+import javax.inject.Inject;
+import javafx.stage.Stage;
+
+public class NoKeychainController implements FxController {
+
+	private final Stage window;
+	private final FxApplicationWindows appWindows;
+
+	@Inject
+	public NoKeychainController(@KeyLoading Stage window, FxApplicationWindows appWindows) {
+		this.window = window;
+		this.appWindows = appWindows;
+	}
+
+
+	public void cancel() {
+		window.close();
+	}
+
+	public void openPreferences() {
+		appWindows.showPreferencesWindow(SelectedPreferencesTab.GENERAL);
+		window.close();
+	}
+}

+ 11 - 2
src/main/java/org/cryptomator/ui/traymenu/AwtTrayMenuController.java

@@ -39,8 +39,8 @@ public class AwtTrayMenuController implements TrayMenuController {
 	}
 
 	@Override
-	public void showTrayIcon(byte[] rawImageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
-		var image = Toolkit.getDefaultToolkit().createImage(rawImageData);
+	public void showTrayIcon(byte[] imageData, Runnable defaultAction, String tooltip) throws TrayMenuException {
+		var image = Toolkit.getDefaultToolkit().createImage(imageData);
 		trayIcon = new TrayIcon(image, tooltip, menu);
 
 		trayIcon.setImageAutoSize(true);
@@ -56,6 +56,15 @@ public class AwtTrayMenuController implements TrayMenuController {
 		}
 	}
 
+	@Override
+	public void updateTrayIcon(byte[] imageData) {
+		if (trayIcon == null) {
+			throw new IllegalStateException("Failed to update the icon as it has not yet been added");
+		}
+		var image = Toolkit.getDefaultToolkit().createImage(imageData);
+		trayIcon.setImage(image);
+	}
+
 	@Override
 	public void updateTrayMenu(List<TrayMenuItem> items) {
 		menu.removeAll();

+ 24 - 5
src/main/java/org/cryptomator/ui/traymenu/TrayMenuBuilder.java

@@ -33,7 +33,9 @@ public class TrayMenuBuilder {
 
 	private static final Logger LOG = LoggerFactory.getLogger(TrayMenuBuilder.class);
 	private static final String TRAY_ICON_MAC = "/img/tray_icon_mac@2x.png";
-	private static final String TRAY_ICON = "/img/window_icon_32.png";
+	private static final String TRAY_ICON_UNLOCKED_MAC = "/img/tray_icon_unlocked_mac@2x.png";
+	private static final String TRAY_ICON = "/img/tray_icon.png";
+	private static final String TRAY_ICON_UNLOCKED = "/img/tray_icon_unlocked.png";
 
 	private final ResourceBundle resourceBundle;
 	private final VaultService vaultService;
@@ -62,8 +64,8 @@ public class TrayMenuBuilder {
 			v.displayNameProperty().addListener(this::vaultListChanged);
 		});
 
-		try (var image = getClass().getResourceAsStream(SystemUtils.IS_OS_MAC_OSX ? TRAY_ICON_MAC : TRAY_ICON)) {
-			trayMenu.showTrayIcon(image.readAllBytes(), this::showMainWindow, "Cryptomator");
+		try {
+			trayMenu.showTrayIcon(getAppropriateTrayIconImage(), this::showMainWindow, "Cryptomator");
 			trayMenu.onBeforeOpenMenu(() -> {
 				for (Vault vault : vaults) {
 					VaultListManager.redetermineVaultState(vault);
@@ -71,8 +73,6 @@ public class TrayMenuBuilder {
 			});
 			rebuildMenu();
 			initialized = true;
-		} catch (IOException e) {
-			throw new UncheckedIOException("Failed to load embedded resource", e);
 		} catch (TrayMenuException e) {
 			LOG.error("Adding tray icon failed", e);
 		}
@@ -84,6 +84,7 @@ public class TrayMenuBuilder {
 
 	private void vaultListChanged(@SuppressWarnings("unused") Observable observable) {
 		assert Platform.isFxApplicationThread();
+		trayMenu.updateTrayIcon(getAppropriateTrayIconImage());
 		rebuildMenu();
 	}
 
@@ -154,4 +155,22 @@ public class TrayMenuBuilder {
 		appWindows.showPreferencesWindow(SelectedPreferencesTab.ANY);
 	}
 
+	private byte[] getAppropriateTrayIconImage() {
+		boolean isAnyVaultUnlocked = vaults.stream().anyMatch(Vault::isUnlocked);
+
+		String resourceName;
+		if (SystemUtils.IS_OS_MAC_OSX) {
+			resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED_MAC : TRAY_ICON_MAC;
+		} else {
+			resourceName = isAnyVaultUnlocked ? TRAY_ICON_UNLOCKED : TRAY_ICON;
+		}
+
+		try (var image = getClass().getResourceAsStream(resourceName)) {
+			assert image != null;
+			return image.readAllBytes();
+		} catch (IOException e) {
+			throw new UncheckedIOException("Failed to load tray icon image: " + resourceName, e);
+		}
+	}
+
 }

+ 56 - 0
src/main/resources/fxml/hub_no_keychain.fxml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
+<?import org.cryptomator.ui.controls.FontAwesome5Spinner?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.Group?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.Region?>
+<?import javafx.scene.layout.StackPane?>
+<?import javafx.scene.layout.VBox?>
+<?import javafx.scene.shape.Circle?>
+<?import org.cryptomator.ui.controls.FormattedLabel?>
+<HBox xmlns:fx="http://javafx.com/fxml"
+	  xmlns="http://javafx.com/javafx"
+	  fx:controller="org.cryptomator.ui.keyloading.hub.NoKeychainController"
+	  minWidth="400"
+	  maxWidth="400"
+	  minHeight="145"
+	  spacing="12"
+	  alignment="TOP_LEFT">
+	<padding>
+		<Insets topRightBottomLeft="12"/>
+	</padding>
+	<children>
+		<Group>
+			<StackPane>
+				<padding>
+					<Insets topRightBottomLeft="6"/>
+				</padding>
+				<Circle styleClass="glyph-icon-primary" radius="24"/>
+				<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
+			</StackPane>
+		</Group>
+
+		<VBox HBox.hgrow="ALWAYS">
+			<Label styleClass="label-large" text="%hub.noKeychain.message" wrapText="true" textAlignment="LEFT">
+				<padding>
+					<Insets bottom="6" top="6"/>
+				</padding>
+			</Label>
+
+			<FormattedLabel format="%hub.noKeychain.description" arg1="%preferences.general.keychainBackend" wrapText="true" textAlignment="LEFT"/>
+
+			<Region VBox.vgrow="ALWAYS" minHeight="18"/>
+			<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
+				<buttons>
+					<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="false" cancelButton="true" onAction="#cancel"/>
+					<Button text="%hub.noKeychain.openBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#openPreferences"/>
+				</buttons>
+			</ButtonBar>
+		</VBox>
+	</children>
+</HBox>

+ 3 - 2
src/main/resources/i18n/strings.properties

@@ -130,6 +130,9 @@ unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory,
 unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing.
 unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already in use.
 ## Hub
+hub.noKeychain.message=Unable to access device key
+hub.noKeychain.description=In order to unlock Hub vaults, a device key is required, which is secured using a keychain. To proceed, enable “%s” and select a keychain in the preferences.
+hub.noKeychain.openBtn=Open Preferences
 ### Waiting
 hub.auth.message=Waiting for authentication…
 hub.auth.description=You should automatically be redirected to the login page.
@@ -220,8 +223,6 @@ health.check.detail.checkFinishedAndFound=The check finished running. Please rev
 health.check.detail.checkFailed=The check exited due to an error.
 health.check.detail.checkCancelled=The check was cancelled.
 health.check.detail.listFilters.label=Filter
-health.check.detail.listFilters.severity=Severity
-health.check.detail.listFilters.fixState=Fix state
 health.check.detail.fixAllSpecificBtn=Fix all of type
 health.check.exportBtn=Export Report
 ## Result view

binární
src/main/resources/img/tray_icon.png


binární
src/main/resources/img/tray_icon_mac.png


binární
src/main/resources/img/tray_icon_unlocked.png


binární
src/main/resources/img/tray_icon_unlocked_mac@2x.png