Skip to content

Commit ef7ee80

Browse files
committed
Use a merged JVM+OS trust store as default SSLContext
Merge JVM and OS trust stores in case opt-in system property eclipse.platform.mergeTrust is set and trust is not otherwise customized via the javax.net.ssl.trustStore[...] system properties. Resolves: https://bugs.eclipse.org/bugs/show_bug.cgi?id=567504 #1690 Obsoletes (to-be-reverted): eclipse-platform/eclipse.platform.releng.aggregator#929 eclipse-packaging/packages#224 Replaces: eclipse-equinox/equinox#1176 See also: eclipse-simrel/.github#38
1 parent 39dbe3c commit ef7ee80

File tree

9 files changed

+858
-0
lines changed

9 files changed

+858
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 SAP SE and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* SAP SE - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.core.internal.runtime;
15+
16+
import java.security.cert.CertificateException;
17+
import java.security.cert.X509Certificate;
18+
import java.util.Arrays;
19+
import java.util.List;
20+
import java.util.Objects;
21+
import javax.net.ssl.X509TrustManager;
22+
23+
public class CollectionTrustManager implements X509TrustManager {
24+
25+
private final List<X509TrustManager> trustManagers;
26+
27+
public CollectionTrustManager(List<X509TrustManager> trustManagers) {
28+
this.trustManagers = trustManagers;
29+
}
30+
31+
public List<X509TrustManager> getTrustManagers() {
32+
return trustManagers;
33+
}
34+
35+
@Override
36+
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
37+
CertificateException ce = null;
38+
for (X509TrustManager trustManager : this.getTrustManagers()) {
39+
try {
40+
trustManager.checkClientTrusted(chain, authType);
41+
return;
42+
} catch (CertificateException e) {
43+
if (ce == null) {
44+
ce = e;
45+
} else {
46+
ce.addSuppressed(e);
47+
}
48+
}
49+
}
50+
if (ce != null) {
51+
throw ce;
52+
}
53+
}
54+
55+
@Override
56+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
57+
CertificateException ce = null;
58+
for (X509TrustManager trustManager : this.getTrustManagers()) {
59+
try {
60+
trustManager.checkServerTrusted(chain, authType);
61+
return;
62+
} catch (CertificateException e) {
63+
if (ce == null) {
64+
ce = e;
65+
} else {
66+
ce.addSuppressed(e);
67+
}
68+
}
69+
}
70+
if (ce != null) {
71+
throw ce;
72+
}
73+
}
74+
75+
@Override
76+
public X509Certificate[] getAcceptedIssuers() {
77+
return this.getTrustManagers().stream() //
78+
.map(X509TrustManager::getAcceptedIssuers) //
79+
.filter(Objects::nonNull).flatMap(Arrays::stream).toArray(X509Certificate[]::new);
80+
}
81+
82+
}

runtime/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/InternalPlatform.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.eclipse.core.runtime.Plugin;
4949
import org.eclipse.core.runtime.RegistryFactory;
5050
import org.eclipse.core.runtime.ServiceCaller;
51+
import org.eclipse.core.runtime.Status;
5152
import org.eclipse.core.runtime.content.IContentTypeManager;
5253
import org.eclipse.core.runtime.preferences.IPreferencesService;
5354
import org.eclipse.equinox.app.IApplicationContext;
@@ -585,6 +586,14 @@ private void initializeAuthorizationHandler() {
585586
}
586587
}
587588

589+
private void initializeSSLContext() {
590+
try {
591+
new KeyStoreUtil(getOS()).setUpSslContext();
592+
} catch (Exception e) {
593+
RuntimeLog.log(Status.error("Exception setting up SSLContext", e)); //$NON-NLS-1$
594+
}
595+
}
596+
588597
/*
589598
* Finds and loads the options file
590599
*/
@@ -701,6 +710,7 @@ public void start(BundleContext runtimeContext) {
701710
initialized = true;
702711
stopped = false;
703712
initializeAuthorizationHandler();
713+
initializeSSLContext();
704714
startServices();
705715
}
706716

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 SAP SE and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* SAP SE - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.core.internal.runtime;
15+
16+
import java.io.FileInputStream;
17+
import java.io.IOException;
18+
import java.io.InputStream;
19+
import java.security.GeneralSecurityException;
20+
import java.security.KeyManagementException;
21+
import java.security.KeyStore;
22+
import java.security.SecureRandom;
23+
import java.util.ArrayList;
24+
import java.util.Arrays;
25+
import java.util.List;
26+
import javax.net.ssl.KeyManager;
27+
import javax.net.ssl.KeyManagerFactory;
28+
import javax.net.ssl.SSLContext;
29+
import javax.net.ssl.TrustManager;
30+
import javax.net.ssl.TrustManagerFactory;
31+
import javax.net.ssl.X509KeyManager;
32+
import javax.net.ssl.X509TrustManager;
33+
import org.eclipse.osgi.service.environment.Constants;
34+
35+
public class KeyStoreUtil {
36+
37+
@SuppressWarnings("nls")
38+
private static final String SYSTEM_PROPERTY_MERGE_TRUST = "eclipse.platform.mergeTrust";
39+
40+
private final String os;
41+
42+
private static final record KeyStoreAndPassword(KeyStore keyStore, char[] password) {
43+
}
44+
45+
public KeyStoreUtil(String os) {
46+
this.os = os;
47+
}
48+
49+
@SuppressWarnings("nls")
50+
public void setUpSslContext() throws GeneralSecurityException, IOException {
51+
52+
if (!Boolean.getBoolean(SYSTEM_PROPERTY_MERGE_TRUST)) {
53+
return;
54+
}
55+
56+
List<KeyStoreAndPassword> keyStores = new ArrayList<>();
57+
// null will loads JVM cacerts OR store indicated by "javax.net.ssl.trustStore" properties
58+
keyStores.add(new KeyStoreAndPassword(null, null));
59+
if (System.getProperty("javax.net.ssl.trustStore", "").isEmpty()) {
60+
if (Constants.OS_MACOSX.equals(os)) {
61+
keyStores.add(createKeyStore("KeychainStore", "Apple"));
62+
} else if (Constants.OS_WIN32.equals(os)) {
63+
keyStores.add(createKeyStore("Windows-ROOT", null));
64+
}
65+
}
66+
List<X509TrustManager> trustManagers = new ArrayList<>();
67+
for (KeyStoreAndPassword storeAndPassword : keyStores) {
68+
trustManagers.add(createX509TrustManager(storeAndPassword.keyStore()));
69+
}
70+
TrustManager[] tm = { new CollectionTrustManager(trustManagers) };
71+
72+
KeyManager[] km = {};
73+
KeyStoreAndPassword keyStore = createKeyStoreFromSystemProperties();
74+
if (keyStore != null) {
75+
km = new KeyManager[] { createX509KeyManager(keyStore.keyStore(), keyStore.password()) };
76+
}
77+
78+
SSLContext sslContext = SSLContext.getInstance("TLS");
79+
initSSLContext(sslContext, tm, km, null);
80+
SSLContext.setDefault(sslContext);
81+
}
82+
83+
private KeyStoreAndPassword createKeyStore(String type, String provider)
84+
throws GeneralSecurityException, IOException {
85+
KeyStore keyStore;
86+
if (provider == null) {
87+
keyStore = KeyStore.getInstance(type);
88+
} else {
89+
keyStore = KeyStore.getInstance(type, provider);
90+
}
91+
keyStore.load(null, null);
92+
return new KeyStoreAndPassword(keyStore, null);
93+
}
94+
95+
protected X509TrustManager createX509TrustManager(KeyStore keyStore) throws GeneralSecurityException {
96+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
97+
tmf.init(keyStore);
98+
return Arrays.stream(tmf.getTrustManagers()).filter(X509TrustManager.class::isInstance) //
99+
.map(X509TrustManager.class::cast) //
100+
.findFirst().orElse(null);
101+
}
102+
103+
protected X509KeyManager createX509KeyManager(KeyStore keyStore, char[] password) throws GeneralSecurityException {
104+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
105+
kmf.init(keyStore, password);
106+
return Arrays.stream(kmf.getKeyManagers()).filter(X509KeyManager.class::isInstance) //
107+
.map(X509KeyManager.class::cast) //
108+
.findFirst().orElse(null);
109+
}
110+
111+
protected void initSSLContext(SSLContext context, TrustManager[] trustManagers, KeyManager[] keyManagers,
112+
SecureRandom random) throws KeyManagementException {
113+
context.init(keyManagers, trustManagers, random);
114+
}
115+
116+
/**
117+
* Coding based on
118+
* sun.security.ssl.SSLContextImpl.DefaultManagersHolder.getKeyManagers() with
119+
* minor adjustments (access properties directy without AccessController, return
120+
* nothing if properties not set).
121+
*/
122+
@SuppressWarnings("nls")
123+
private KeyStoreAndPassword createKeyStoreFromSystemProperties() throws GeneralSecurityException, IOException {
124+
String p11KeyStore = "PKCS11";
125+
String none = "NONE";
126+
String keyStore = System.getProperty("javax.net.ssl.keyStore", "");
127+
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", "");
128+
String keyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", "");
129+
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
130+
131+
if (keyStoreType.isEmpty()) {
132+
if (keyStore.isEmpty()) {
133+
return null;
134+
}
135+
keyStoreType = KeyStore.getDefaultType();
136+
}
137+
if (p11KeyStore.equals(keyStoreType) && !none.equals(keyStore)) {
138+
throw new IllegalArgumentException("if keyStoreType is " + p11KeyStore + ", then keyStore must be " + none);
139+
}
140+
char[] passwd = null;
141+
if (!keyStorePassword.isEmpty()) {
142+
passwd = keyStorePassword.toCharArray();
143+
}
144+
KeyStore ks = null;
145+
if (keyStoreProvider.isEmpty()) {
146+
ks = KeyStore.getInstance(keyStoreType);
147+
} else {
148+
ks = KeyStore.getInstance(keyStoreType, keyStoreProvider);
149+
}
150+
if (!keyStore.isEmpty() && !none.equals(keyStore)) {
151+
try (InputStream is = new FileInputStream(keyStore)) {
152+
ks.load(is, passwd);
153+
}
154+
} else {
155+
ks.load(null, passwd);
156+
}
157+
return new KeyStoreAndPassword(ks, p11KeyStore.equals(keyStoreType) ? null : passwd);
158+
}
159+
160+
}

runtime/tests/org.eclipse.core.tests.runtime/META-INF/MANIFEST.MF

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Require-Bundle: org.junit,
1616
org.eclipse.core.tests.harness;bundle-version="3.11.0"
1717
Import-Package: org.assertj.core.api,
1818
org.junit.jupiter.api;version="[5.14.0,6.0.0)",
19+
org.junit.jupiter.api.condition;version="[5.14.0,6.0.0)",
1920
org.junit.jupiter.api.extension;version="[5.14.0,6.0.0)",
2021
org.junit.jupiter.api.function;version="[5.14.0,6.0.0)",
2122
org.junit.jupiter.api.io;version="[5.14.0,6.0.0)",

runtime/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/AllInternalRuntimeTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
@Suite
2020
@SelectClasses({ //
21+
CollectionTrustManagerTest.class, //
22+
KeyStoreUtilTest.class, //
2123
LogSerializationTest.class, //
2224
PlatformURLLocalTest.class, //
2325
PlatformURLSessionTest.class })

0 commit comments

Comments
 (0)