From 03d9a448171598b6eb30a96fc72efb6f5b3b56c4 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 21 Oct 2025 12:14:20 +0200 Subject: [client] Ignore all exceptions while iterating over trust managers Try harder to avoid crashing and burning if one of the trust managers contained in the combined trust manager acts up. Ignore all runtime exceptions while iterating, and if nothing worked in the end, throw a synthetic CertificateException. --- .../src/main/java/org/openslx/dozmod/App.java | 4 +- .../openslx/dozmod/util/CombinedTrustManager.java | 197 +++++++++++++++++++++ .../openslx/dozmod/util/FallbackTrustManager.java | 191 -------------------- .../org/openslx/dozmod/util/ProxyConfigurator.java | 6 +- 4 files changed, 202 insertions(+), 196 deletions(-) create mode 100644 dozentenmodul/src/main/java/org/openslx/dozmod/util/CombinedTrustManager.java delete mode 100644 dozentenmodul/src/main/java/org/openslx/dozmod/util/FallbackTrustManager.java (limited to 'dozentenmodul/src') diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/App.java b/dozentenmodul/src/main/java/org/openslx/dozmod/App.java index a5dc7464..7d31b54f 100755 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/App.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/App.java @@ -37,7 +37,7 @@ import org.openslx.dozmod.gui.helper.I18n; import org.openslx.dozmod.gui.helper.Language; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.util.ClientVersion; -import org.openslx.dozmod.util.FallbackTrustManager; +import org.openslx.dozmod.util.CombinedTrustManager; import org.openslx.dozmod.util.ProxyConfigurator; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.AppUtil; @@ -148,7 +148,7 @@ public class App { LOGGER.info("Starting logging to " + logFilePath); // On Windows, we use the system's trust store in addition to the Java one - FallbackTrustManager.install(); + CombinedTrustManager.install(); // Setting the locale if (!setPreferredLanguage()) { diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/CombinedTrustManager.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/CombinedTrustManager.java new file mode 100644 index 00000000..8bcd6bfa --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/util/CombinedTrustManager.java @@ -0,0 +1,197 @@ +package org.openslx.dozmod.util; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CombinedTrustManager { + + private final static Logger LOGGER = LogManager.getLogger(CombinedTrustManager.class); + + private static SSLContext sslContext = null; + + private static CombinedX509TrustManager delegatingTrustManager = null; + + public static void install() { + // On Windows, use system store in addition to the Java one + List managers = new ArrayList<>(); + char[] password = "changeit".toCharArray(); + + LOGGER.info("Installing Fallback X509 truster"); + + try { + // --- Load Java default trust store (cacerts) --- + String javaHome = System.getProperty("java.home"); + String cacertsPath = javaHome + "/lib/security/cacerts"; + KeyStore javaTrustStore = KeyStore.getInstance("JKS"); + + try (FileInputStream fis = new FileInputStream(cacertsPath)) { + javaTrustStore.load(fis, password); + } + addKeyStore(managers, javaTrustStore, "Java"); + } catch (Exception e) { + LOGGER.warn("Error adding java certificate store", e); + } + + if (OsHelper.isWindows()) { + try { + // --- Load Windows root store --- + KeyStore winRootStore = KeyStore.getInstance("Windows-ROOT"); + winRootStore.load(null, null); + addKeyStore(managers, winRootStore, "Windows-ROOT"); + } catch (Exception e) { + LOGGER.warn("Error adding Windows-ROOT certificate store", e); + } + try { + // --- Load Windows user store --- + KeyStore winUserStore = KeyStore.getInstance("Windows-MY"); + winUserStore.load(null, null); + addKeyStore(managers, winUserStore, "Windows-MY"); + } catch (Exception e) { + LOGGER.warn("Error adding Windows-MY certificate store", e); + } + } + + try { + KeyStore shippedStore = KeyStore.getInstance("JKS"); + try (InputStream is = ResourceLoader.getStream("/data/truststore.jks")) { + shippedStore.load(is, password); + } + addKeyStore(managers, shippedStore, "Shipped"); + } catch (Exception e) { + LOGGER.warn("Error adding shipped certificate store", e); + } + + if (managers.isEmpty()) { + LOGGER.warn("Couldn't load any trust manager - using default one"); + return; + } + + try { + // --- Combine using delegating trust manager --- + delegatingTrustManager = new CombinedX509TrustManager(managers); + + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, getTrustManagers(), null); + SSLContext.setDefault(sslContext); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); + } catch (Exception e) { + LOGGER.warn("Error installing custom trust manager", e); + } + } + + private static void addKeyStore(List list, KeyStore store, String name) + throws Exception { + LOGGER.info(name + " entries: " + store.size()); + if (store.size() == 0) + return; // Empty ones cause problems + TrustManagerFactory javaTMF = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + javaTMF.init(store); + X509TrustManager javaTrustManager = getX509TrustManager(javaTMF); + list.add(javaTrustManager); + } + + public static TrustManager getTrustManager() { + return delegatingTrustManager; + } + + public static TrustManager[] getTrustManagers() { + if (delegatingTrustManager == null) + return null; + return new TrustManager[] { delegatingTrustManager }; + } + + // Extract the first X509TrustManager from the factory + private static X509TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception { + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + return (X509TrustManager) tm; + } + } + throw new IllegalStateException("No X509TrustManager found"); + } + + // Delegating trust manager implementation + public static class CombinedX509TrustManager implements X509TrustManager { + private final List managers; + private X509Certificate[] issuers = null; + + public CombinedX509TrustManager(List managers) { + this.managers = managers; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + CertificateException cached = null; + + for (X509TrustManager tm : managers) { + try { + tm.checkClientTrusted(chain, authType); + return; + } catch (CertificateException e) { + cached = e; + } catch (RuntimeException rte) { + LOGGER.warn("Other exception in checkClientTrusted", rte); + } + } + if (cached != null) + throw cached; + throw new CertificateException("Unknown exception in combined trust manager"); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + CertificateException cached = null; + + for (X509TrustManager tm : managers) { + try { + tm.checkServerTrusted(chain, authType); + return; + } catch (CertificateException e) { + cached = e; + } catch (RuntimeException rte) { + LOGGER.warn("Other exception in checkServerTrusted", rte); + } + } + if (cached != null) + throw cached; + throw new CertificateException("Unknown exception in combined trust manager"); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + if (issuers == null) { + Set certs = new HashSet<>(); + for (X509TrustManager tm : managers) { + try { + certs.addAll(Arrays.asList(tm.getAcceptedIssuers())); + } catch (Exception e) { + LOGGER.warn("Error adding accepted issuers to combined return value", e); + } + } + issuers = certs.toArray(new X509Certificate[certs.size()]); + } + return issuers; + } + } + +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/FallbackTrustManager.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/FallbackTrustManager.java deleted file mode 100644 index 3d652ba0..00000000 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/util/FallbackTrustManager.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.openslx.dozmod.util; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class FallbackTrustManager { - - private final static Logger LOGGER = LogManager.getLogger(FallbackTrustManager.class); - - private static SSLContext sslContext = null; - - private static FallbackX509TrustManager delegatingTrustManager = null; - - public static void install() { - // On Windows, use system store in addition to the Java one - List managers = new ArrayList<>(); - char[] password = "changeit".toCharArray(); - - LOGGER.info("Installing Fallback X509 truster"); - - try { - // --- Load Java default trust store (cacerts) --- - String javaHome = System.getProperty("java.home"); - String cacertsPath = javaHome + "/lib/security/cacerts"; - - KeyStore javaTrustStore = KeyStore.getInstance("JKS"); - try (FileInputStream fis = new FileInputStream(cacertsPath)) { - javaTrustStore.load(fis, password); - } - - TrustManagerFactory javaTMF = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - javaTMF.init(javaTrustStore); - LOGGER.info("Java entries: " + javaTrustStore.size()); - X509TrustManager javaTrustManager = getX509TrustManager(javaTMF); - managers.add(javaTrustManager); - } catch (Exception e) { - LOGGER.warn("Error adding java certificate store", e); - } - - if (OsHelper.isWindows()) { - try { - // --- Load Windows root store --- - KeyStore systemRoot = KeyStore.getInstance("Windows-ROOT"); - systemRoot.load(null, null); - TrustManagerFactory windowsTMF = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - windowsTMF.init(systemRoot); - LOGGER.info("System entries: " + systemRoot.size()); - X509TrustManager windowsTrustManager = getX509TrustManager(windowsTMF); - managers.add(windowsTrustManager); - } catch (Exception e) { - LOGGER.warn("Error adding Windows-ROOT certificate store", e); - } - try { - // --- Load Windows root store --- - KeyStore systemRoot = KeyStore.getInstance("Windows-MY"); - systemRoot.load(null, null); - TrustManagerFactory windowsTMF = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - windowsTMF.init(systemRoot); - LOGGER.info("User entries: " + systemRoot.size()); - X509TrustManager windowsTrustManager = getX509TrustManager(windowsTMF); - managers.add(windowsTrustManager); - } catch (Exception e) { - LOGGER.warn("Error adding Windows-MY certificate store", e); - } - } - - try { - KeyStore systemRoot = KeyStore.getInstance("JKS"); - try (InputStream is = ResourceLoader.getStream("/data/truststore.jks")) { - systemRoot.load(is, password); - } - TrustManagerFactory windowsTMF = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - windowsTMF.init(systemRoot); - LOGGER.info("Shipped entries: " + systemRoot.size()); - X509TrustManager windowsTrustManager = getX509TrustManager(windowsTMF); - managers.add(windowsTrustManager); - } catch (Exception e) { - LOGGER.warn("Error adding shipped certificate store", e); - } - - if (managers.isEmpty()) { - LOGGER.warn("Couldn't load any trust manager - using default one"); - return; - } - - try { - // --- Combine using delegating trust manager --- - delegatingTrustManager = new FallbackX509TrustManager(managers); - - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, getTrustManagers(), null); - SSLContext.setDefault(sslContext); - HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); - } catch (Exception e) { - LOGGER.warn("Error installing custom trust manager", e); - } - } - - public static TrustManager getTrustManager() { - return delegatingTrustManager; - } - - public static TrustManager[] getTrustManagers() { - if (delegatingTrustManager == null) - return null; - return new TrustManager[] { delegatingTrustManager }; - } - - // Extract the first X509TrustManager from the factory - private static X509TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception { - for (TrustManager tm : tmf.getTrustManagers()) { - if (tm instanceof X509TrustManager) { - return (X509TrustManager) tm; - } - } - throw new IllegalStateException("No X509TrustManager found"); - } - - // Delegating trust manager implementation - public static class FallbackX509TrustManager implements X509TrustManager { - private final List managers; - private X509Certificate[] issuers = null; - - public FallbackX509TrustManager(List managers) { - this.managers = managers; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - java.security.cert.CertificateException cached = null; - for (X509TrustManager tm : managers) { - try { - tm.checkClientTrusted(chain, authType); - return; - } catch (java.security.cert.CertificateException e) { - cached = e; - } - } - throw cached; - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - java.security.cert.CertificateException cached = null; - for (X509TrustManager tm : managers) { - try { - tm.checkServerTrusted(chain, authType); - return; - } catch (java.security.cert.CertificateException e) { - cached = e; - } - } - throw cached; - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - if (issuers == null) { - Set certs = new HashSet<>(); - for (X509TrustManager tm : managers) { - certs.addAll(Arrays.asList(tm.getAcceptedIssuers())); - } - issuers = certs.toArray(new X509Certificate[certs.size()]); - } - return issuers; - } - } - -} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ProxyConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ProxyConfigurator.java index b024dae5..1d9a7b88 100644 --- a/dozentenmodul/src/main/java/org/openslx/dozmod/util/ProxyConfigurator.java +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/util/ProxyConfigurator.java @@ -83,7 +83,7 @@ public class ProxyConfigurator { } else { thriftCtx = SSLContext.getInstance("TLSv1.2"); } - thriftCtx.init(null, FallbackTrustManager.getTrustManagers(), null); + thriftCtx.init(null, CombinedTrustManager.getTrustManagers(), null); } catch (NoSuchAlgorithmException | KeyManagementException e) { LOGGER.warn("Error creating default SSL context for thrift", e); } @@ -98,7 +98,7 @@ public class ProxyConfigurator { MasterServer.Client masterClient; try { ctx = SSLContext.getInstance(tls[0].id); - ctx.init(null, FallbackTrustManager.getTrustManagers(), null); + ctx.init(null, CombinedTrustManager.getTrustManagers(), null); masterClient = ThriftManager.getNewMasterClient(ctx, App.getMasterServerAddress(), App.THRIFT_SSL_PORT, 4000); @@ -148,7 +148,7 @@ public class ProxyConfigurator { if (thriftCtx == null) { try { SSLContext ctx = SSLContext.getDefault(); - ctx.init(null, FallbackTrustManager.getTrustManagers(), null); + ctx.init(null, CombinedTrustManager.getTrustManagers(), null); thriftCtx = ctx; } catch (Exception e) { Gui.asyncMessageBox(I18n.GUI.getString("ProxyConfigurator.Message.error.couldNotGetSslContext"), -- cgit v1.2.3-55-g7522