diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 6a2be344e..33bea18cc 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -116,6 +116,7 @@ jobs: --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\"" + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" --resource-dir dist/linux/resources - name: Patch Cryptomator.AppDir run: | diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 43f0a4011..ab42ec97e 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -89,7 +89,7 @@ jobs: --verbose --output runtime --module-path "jfxjmods;${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index cfa8f1e98..3322b6824 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -91,6 +91,7 @@ ${JAVA_HOME}/bin/jpackage \ --java-options "-Dcryptomator.showTrayIcon=true" \ --java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \ --java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --resource-dir ../resources # transform AppDir diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index 0a52d4512..a899dba68 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -62,6 +62,7 @@ override_dh_auto_build: --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \ --java-options "-Dcryptomator.integrationsLinux.autoStartCmd=\"cryptomator\"" \ + --java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ --verbose diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 61da2106e..9d01f773d 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -75,7 +75,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja --verbose ` --output runtime ` --module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods" ` - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --strip-native-commands ` --no-header-files ` --no-man-pages ` diff --git a/pom.xml b/pom.xml index 09ab25624..d00028ef6 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 2.7.2 - 1.4.0 + 1.5.0 1.3.0 1.2.4 1.5.1 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 6897d8c53..4dd4242b3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,4 +1,5 @@ import ch.qos.logback.classic.spi.Configurator; +import org.cryptomator.networking.SSLContextWithPKCS12TrustStore; import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider; @@ -13,6 +14,9 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider; import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider; import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider; +import org.cryptomator.networking.SSLContextWithMacKeychain; +import org.cryptomator.networking.SSLContextProvider; +import org.cryptomator.networking.SSLContextWithWindowsCertStore; import org.cryptomator.integrations.tray.TrayMenuController; import org.cryptomator.logging.LogbackConfiguratorFactory; import org.cryptomator.ui.traymenu.AwtTrayMenuController; @@ -54,9 +58,11 @@ open module org.cryptomator.desktop { requires com.github.benmanes.caffeine; uses org.cryptomator.common.locationpresets.LocationPresetsProvider; + uses SSLContextProvider; provides TrayMenuController with AwtTrayMenuController; provides Configurator with LogbackConfiguratorFactory; + provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore; provides LocationPresetsProvider with // DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, // GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, // diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index a8d38c312..3e0a613ca 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -9,8 +9,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; import dagger.Lazy; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; -import org.cryptomator.common.SubstitutingProperties; import org.cryptomator.common.ShutdownHook; +import org.cryptomator.common.SubstitutingProperties; +import org.cryptomator.networking.SSLContextProvider; import org.cryptomator.ipc.IpcCommunicator; import org.cryptomator.logging.DebugMode; import org.cryptomator.ui.fxapp.FxApplicationComponent; @@ -19,8 +20,10 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; +import javax.net.ssl.SSLContext; import javafx.application.Application; import javafx.stage.Stage; +import java.security.SecureRandom; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -48,14 +51,16 @@ public class Cryptomator { private final Environment env; private final Lazy ipcMessageHandler; private final ShutdownHook shutdownHook; + private final SecureRandom csprng; @Inject - Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook) { + Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy ipcMessageHandler, ShutdownHook shutdownHook, SecureRandom csprng) { this.debugMode = debugMode; this.supportedLanguages = supportedLanguages; this.env = env; this.ipcMessageHandler = ipcMessageHandler; this.shutdownHook = shutdownHook; + this.csprng = csprng; } public static void main(String[] args) { @@ -89,7 +94,7 @@ public class Cryptomator { LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); debugMode.initialize(); supportedLanguages.applyPreferred(); - + changeDefaultSSLContext(); /* * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. * If no external process could be reached, the args will be handled by the loopback IPC endpoint. @@ -115,6 +120,17 @@ public class Cryptomator { } } + private void changeDefaultSSLContext() { + SSLContextProvider.loadAll().findFirst().ifPresent(p -> { + try { + var context = p.getContext(csprng); + SSLContext.setDefault(context); + } catch (SSLContextProvider.SSLContextBuildException e) { + LOG.warn("Failed to change default SSL context with provider {}", p.getClass().getName(), e); + } + }); + } + /** * Launches the JavaFX application, blocking the main thread until shuts down. * diff --git a/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java new file mode 100644 index 000000000..87e1b82ef --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextDifferentTrustStoreBase.java @@ -0,0 +1,33 @@ +package org.cryptomator.networking; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; + +abstract class SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + abstract KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException; + + @Override + public SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException { + try { + KeyStore truststore = getTruststore(); + truststore.load(null, null); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(truststore); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), csprng); + return context; + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | KeyManagementException | IOException e) { + throw new SSLContextBuildException(e); + } + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextProvider.java b/src/main/java/org/cryptomator/networking/SSLContextProvider.java new file mode 100644 index 000000000..3563c546c --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextProvider.java @@ -0,0 +1,24 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.IntegrationsLoader; + +import javax.net.ssl.SSLContext; +import java.security.SecureRandom; +import java.util.ServiceLoader; +import java.util.stream.Stream; + +public interface SSLContextProvider { + + SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException; + + class SSLContextBuildException extends Exception { + + SSLContextBuildException(Throwable t) { + super(t); + } + } + + static Stream loadAll() { + return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java new file mode 100644 index 000000000..0078fdfd3 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithMacKeychain.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +/** + * SSLContextProvider for macOS using the macOS Keychain as truststore + */ +@OperatingSystem(OperatingSystem.Value.MAC) +public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + return KeyStore.getInstance("KeychainStore-ROOT"); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java new file mode 100644 index 000000000..f530435f8 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithPKCS12TrustStore.java @@ -0,0 +1,42 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.CheckAvailability; +import org.cryptomator.integrations.common.OperatingSystem; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Optional; + +/** + * SSLContextProvider for Linux using a PKCS#12 file as trust store + */ +@OperatingSystem(OperatingSystem.Value.LINUX) +@CheckAvailability +public class SSLContextWithPKCS12TrustStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + private static final String CERT_FILE_LOCATION_PROPERTY = "cryptomator.networking.truststore.p12Path"; + + @Override + KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { + var pkcs12FilePath = Path.of(System.getProperty(CERT_FILE_LOCATION_PROPERTY)); + try { + return KeyStore.getInstance(pkcs12FilePath.toFile(), new char[]{}); + } catch (IllegalArgumentException e) { + throw new NoSuchFileException(pkcs12FilePath.toString()); + } + } + + @CheckAvailability + public static boolean isSupported() { + var pkcs12Path = System.getProperty(CERT_FILE_LOCATION_PROPERTY); + return Optional.ofNullable(pkcs12Path) // + .map(Path::of) // + .map(Files::exists).orElse(false); + } +} diff --git a/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java new file mode 100644 index 000000000..0f1ea04b5 --- /dev/null +++ b/src/main/java/org/cryptomator/networking/SSLContextWithWindowsCertStore.java @@ -0,0 +1,21 @@ +package org.cryptomator.networking; + +import org.cryptomator.integrations.common.OperatingSystem; + +import java.security.KeyStore; +import java.security.KeyStoreException; + +/** + * SSLContextProvider for Windows using the Windows certificate store as trust store + *

+ * In order to work, the jdk.crypto.mscapi jmod is needed + */ +@OperatingSystem(OperatingSystem.Value.WINDOWS) +public class SSLContextWithWindowsCertStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider { + + @Override + KeyStore getTruststore() throws KeyStoreException { + return KeyStore.getInstance("WINDOWS-ROOT"); + } + +}