Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*******************************************************************************
* Copyright (c) 2025 SAP SE and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP SE - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.runtime;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.net.ssl.X509TrustManager;

public class CollectionTrustManager implements X509TrustManager {

private final List<X509TrustManager> trustManagers;

public CollectionTrustManager(List<X509TrustManager> trustManagers) {
this.trustManagers = trustManagers;
}

public List<X509TrustManager> getTrustManagers() {
return trustManagers;
}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
CertificateException ce = null;
for (X509TrustManager trustManager : this.getTrustManagers()) {
try {
trustManager.checkClientTrusted(chain, authType);
return;
} catch (CertificateException e) {
if (ce == null) {
ce = e;
} else {
ce.addSuppressed(e);
}
}
}
if (ce != null) {
throw ce;
}
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
CertificateException ce = null;
for (X509TrustManager trustManager : this.getTrustManagers()) {
try {
trustManager.checkServerTrusted(chain, authType);
return;
} catch (CertificateException e) {
if (ce == null) {
ce = e;
} else {
ce.addSuppressed(e);
}
}
}
if (ce != null) {
throw ce;
}
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return this.getTrustManagers().stream() //
.map(X509TrustManager::getAcceptedIssuers) //
.filter(Objects::nonNull).flatMap(Arrays::stream).toArray(X509Certificate[]::new);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.core.runtime.ServiceCaller;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.preferences.IPreferencesService;
import org.eclipse.equinox.app.IApplicationContext;
Expand Down Expand Up @@ -585,6 +586,14 @@ private void initializeAuthorizationHandler() {
}
}

private void initializeSSLContext() {
try {
new KeyStoreUtil(getOS()).setUpSslContext();
} catch (Exception e) {
RuntimeLog.log(Status.error("Exception setting up SSLContext", e)); //$NON-NLS-1$
}
}

/*
* Finds and loads the options file
*/
Expand Down Expand Up @@ -701,6 +710,7 @@ public void start(BundleContext runtimeContext) {
initialized = true;
stopped = false;
initializeAuthorizationHandler();
initializeSSLContext();
startServices();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (c) 2025 SAP SE and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP SE - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.runtime;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.osgi.service.environment.Constants;

public class KeyStoreUtil {

@SuppressWarnings("nls")
private static final String PROP_LOAD_OS_TRUST_STORE_BY_DEFAULT = "eclipse.load.os.trust.store.by.default";

private final String os;

private static final record KeyStoreAndPassword(KeyStore keyStore, char[] password) {
}

public KeyStoreUtil(String os) {
this.os = os;
}

@SuppressWarnings("nls")
public void setUpSslContext() throws GeneralSecurityException, IOException {

if (Boolean.FALSE.toString().equals(System.getProperty(PROP_LOAD_OS_TRUST_STORE_BY_DEFAULT, null))) {
return;
}

List<KeyStoreAndPassword> keyStores = new ArrayList<>();
keyStores.add(new KeyStoreAndPassword(null, null)); // null will loads JVM cacerts OR store indicated by
// "javax.net.ssl.trustStore" properties
if (System.getProperty("javax.net.ssl.trustStore", "").isEmpty()) { // NOPMD
if (Constants.OS_MACOSX.equals(os)) {
keyStores.add(createKeyStore("KeychainStore", "Apple"));
} else if (Constants.OS_WIN32.equals(os)) {
keyStores.add(createKeyStore("Windows-ROOT", null));
}
}
List<X509TrustManager> trustManagers = new ArrayList<>();
for (KeyStoreAndPassword storeAndPassword : keyStores) {
trustManagers.add(createX509TrustManager(storeAndPassword.keyStore()));
}
TrustManager[] tm = { new CollectionTrustManager(trustManagers) };

KeyManager[] km = {};
KeyStoreAndPassword keyStore = createKeyStoreFromSystemProperties();
if (keyStore != null) {
km = new KeyManager[] { createX509KeyManager(keyStore.keyStore(), keyStore.password()) };
}

SSLContext sslContext = SSLContext.getInstance("TLS");
initSSLContext(sslContext, tm, km, null);
SSLContext.setDefault(sslContext);
}

private KeyStoreAndPassword createKeyStore(String type, String provider)
throws GeneralSecurityException, IOException {
KeyStore keyStore;
if (provider == null) {
keyStore = KeyStore.getInstance(type);
} else {
keyStore = KeyStore.getInstance(type, provider);
}
keyStore.load(null, null);
return new KeyStoreAndPassword(keyStore, null);
}

protected X509TrustManager createX509TrustManager(KeyStore keyStore) throws GeneralSecurityException {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
return Arrays.stream(tmf.getTrustManagers()).filter(X509TrustManager.class::isInstance) //
.map(X509TrustManager.class::cast) //
.findFirst().orElse(null);
}

protected X509KeyManager createX509KeyManager(KeyStore keyStore, char[] password) throws GeneralSecurityException {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, password);
return Arrays.stream(kmf.getKeyManagers()).filter(X509KeyManager.class::isInstance) //
.map(X509KeyManager.class::cast) //
.findFirst().orElse(null);
}

protected void initSSLContext(SSLContext context, TrustManager[] trustManagers, KeyManager[] keyManagers,
SecureRandom random) throws KeyManagementException {
context.init(keyManagers, trustManagers, random);
}

/**
* Coding based on
* sun.security.ssl.SSLContextImpl.DefaultManagersHolder.getKeyManagers() with
* minor adjustments (access properties directy without AccessController, return
* nothing if properties not set).
*/
@SuppressWarnings("nls")
private KeyStoreAndPassword createKeyStoreFromSystemProperties() throws GeneralSecurityException, IOException { // NOPMD
String p11KeyStore = "PKCS11";
String none = "NONE";
String keyStore = System.getProperty("javax.net.ssl.keyStore", ""); // NOPMD
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", ""); // NOPMD
String keyStoreProvider = System.getProperty("javax.net.ssl.keyStoreProvider", ""); // NOPMD
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", ""); // NOPMD

if (keyStoreType.isEmpty()) {
if (keyStore.isEmpty()) {
return null;
}
keyStoreType = KeyStore.getDefaultType();
}
if (p11KeyStore.equals(keyStoreType) && !none.equals(keyStore)) {
throw new IllegalArgumentException("if keyStoreType is " + p11KeyStore + ", then keyStore must be " + none);
}
char[] passwd = null;
if (!keyStorePassword.isEmpty()) {
passwd = keyStorePassword.toCharArray();
}
KeyStore ks = null;
if (keyStoreProvider.isEmpty()) {
ks = KeyStore.getInstance(keyStoreType);
} else {
ks = KeyStore.getInstance(keyStoreType, keyStoreProvider);
}
if (!keyStore.isEmpty() && !none.equals(keyStore)) {
try (InputStream is = new FileInputStream(keyStore)) {
ks.load(is, passwd);
}
} else {
ks.load(null, passwd);
}
return new KeyStoreAndPassword(ks, p11KeyStore.equals(keyStoreType) ? null : passwd);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Require-Bundle: org.junit,
org.eclipse.core.tests.harness;bundle-version="3.11.0"
Import-Package: org.assertj.core.api,
org.junit.jupiter.api;version="[5.14.0,6.0.0)",
org.junit.jupiter.api.condition;version="[5.14.0,6.0.0)",
org.junit.jupiter.api.extension;version="[5.14.0,6.0.0)",
org.junit.jupiter.api.function;version="[5.14.0,6.0.0)",
org.junit.jupiter.api.io;version="[5.14.0,6.0.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

@Suite
@SelectClasses({ //
CollectionTrustManagerTest.class, //
KeyStoreUtilTest.class, //
LogSerializationTest.class, //
PlatformURLLocalTest.class, //
PlatformURLSessionTest.class })
Expand Down
Loading
Loading