Browse Source

Feature: Use system certificate stores/custom pkcs12 file (#3675)

* for Windows use Windows certificate
* for macOS use macOS Keychain
* for Linux use a custom PKCS12 file under /etc/cryptomator/certs.p12
Armin Schrenk 2 months ago
parent
commit
3b8fec4c5a

+ 1 - 0
.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: |

+ 1 - 1
.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

+ 1 - 0
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

+ 1 - 0
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

+ 1 - 1
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 `

+ 1 - 1
pom.xml

@@ -34,7 +34,7 @@
 
 		<!-- cryptomator dependencies -->
 		<cryptomator.cryptofs.version>2.7.2</cryptomator.cryptofs.version>
-		<cryptomator.integrations.version>1.4.0</cryptomator.integrations.version>
+		<cryptomator.integrations.version>1.5.0</cryptomator.integrations.version>
 		<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
 		<cryptomator.integrations.mac.version>1.2.4</cryptomator.integrations.mac.version>
 		<cryptomator.integrations.linux.version>1.5.1</cryptomator.integrations.linux.version>

+ 6 - 0
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, //

+ 19 - 3
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> ipcMessageHandler;
 	private final ShutdownHook shutdownHook;
+	private final SecureRandom csprng;
 
 	@Inject
-	Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
+	Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> 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.
 	 *

+ 33 - 0
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);
+		}
+	}
+}

+ 24 - 0
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<SSLContextProvider> loadAll() {
+		return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class);
+	}
+}

+ 21 - 0
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");
+	}
+}

+ 42 - 0
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);
+	}
+}

+ 21 - 0
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
+ * <p>
+ * 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");
+	}
+
+}