Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5b8f1d5
#1750: added Updater for golang support
MarvMa Mar 13, 2026
a13c169
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MarvMa Mar 13, 2026
4f72ebe
#1751: create Go commandlet
MarvMa Mar 16, 2026
16b6a0e
#1751: remove wrongly committed files
MarvMa Mar 16, 2026
e2da970
#1751: added Go tag, fixed naming and added go to the commandletManger
MarvMa Mar 16, 2026
2e39d2f
#1751: added tool installation for go
MarvMa Mar 17, 2026
4945197
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MarvMa Mar 17, 2026
0fe2b3d
#1751: implemented go installation
MarvMa Mar 18, 2026
4a4f561
#1751: updated implementation of go installation, added tests and doc…
MarvMa Mar 18, 2026
e20a599
#1751: added go-lang support for cli to changelog
MarvMa Mar 18, 2026
10f9880
#1687: added argument to suppress native warning
MarvMa Mar 24, 2026
92b1726
merge
MarvMa Mar 24, 2026
dd0067a
Merge branch 'main' into feature/#1687-fix-JLine-warning
hohwille Mar 26, 2026
0905a1f
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MarvMa Mar 26, 2026
189ed57
#1687: updated changelog
MarvMa Mar 26, 2026
5779788
#1552: integrated a truststore commandlet to create a custom truststore
MarvMa Mar 27, 2026
a4d5fdc
#1552: integrated a truststore commandlet to create a custom truststore
MarvMa Mar 27, 2026
3736c9c
#1552: added tests, description for the commandlet and remove changab…
MarvMa Mar 30, 2026
e63fc70
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MarvMa Mar 30, 2026
61b29a8
#1552: added tests for truststore cmdlet
MarvMa Mar 30, 2026
22b773a
#1552: update changelog
MarvMa Mar 30, 2026
a937aac
#1552: log information if certificate related exception occurs
MarvMa Mar 30, 2026
72a5eb1
#1552: applied requested PR changes
MarvMa Mar 31, 2026
ea6dc23
#1552: updated test
MarvMa Mar 31, 2026
2dbe39d
#1552: update test
MarvMa Mar 31, 2026
ecdb045
Merge branch 'main' into feature/#1552-add-certificates-to-truststore
hohwille Apr 2, 2026
acece02
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
MarvMa Apr 2, 2026
fe95ff8
Merge branch 'feature/#1552-add-certificates-to-truststore' of https:…
MarvMa Apr 2, 2026
f8e194e
#1552: requested changes from pr
MarvMa Apr 2, 2026
4e0f71e
Merge branch 'main' into feature/#1552-add-certificates-to-truststore
hohwille Apr 7, 2026
bc30f54
Update cli/src/main/java/com/devonfw/tools/ide/network/NetworkStatusI…
MarvMa Apr 7, 2026
d94dcce
fixed CHANGELOG
hohwille Apr 7, 2026
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
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/1552[#1552]: Add Commandlet to fix TLS issue

The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/43?closed=1[milestone 2026.04.002].

== 2026.04.001
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public CommandletManagerImpl(IdeContext context) {
add(new InstallPluginCommandlet(context));
add(new UninstallPluginCommandlet(context));
add(new UpgradeCommandlet(context));
add(new TruststoreCommandlet(context));
add(new Gh(context));
add(new Helm(context));
add(new Java(context));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Path;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.cli.CliException;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.environment.EnvironmentVariables;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.property.StringProperty;
import com.devonfw.tools.ide.util.TruststoreUtil;

/**
* {@link Commandlet} to fix the TLS problem for VPN users.
*/
public class TruststoreCommandlet extends Commandlet {

private static final Logger LOG = LoggerFactory.getLogger(TruststoreCommandlet.class);

private static final String IDE_OPTIONS = "IDE_OPTIONS";

private static final String TRUSTSTORE_OPTION_PREFIX = "-Djavax.net.ssl.trustStore=";

private static final String TRUSTSTORE_PASSWORD_OPTION_PREFIX = "-Djavax.net.ssl.trustStorePassword=";

private final StringProperty url;


/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public TruststoreCommandlet(IdeContext context) {
super(context);
addKeyword(getName());
this.url = add(new StringProperty("", false, "url"));
}

@Override
public String getName() {
return "fix-vpn-tls-problem";
}

@Override
public boolean isIdeHomeRequired() {
return false;
}

/**
* This commandlet tries to fix TLS problems for VPN users by capturing the untrusted certificate from the target endpoint and adding it to a custom
* truststore. It also configures IDE_OPTIONS to use the custom truststore by default. The commandlet is idempotent and will not make changes if the endpoint
* is already reachable or if the certificate is already trusted.
* <p>
* The flow is as follows:
* <ul>
* <li>Parse the input URL/host and port.</li>
* <li>Check if a custom truststore already exists and can establish a TLS connection to the endpoint. If yes, exit successfully.</li>
* <li>Check if the endpoint is reachable without any certificate changes. If yes, exit successfully.</li>
* <li>Try to capture the server certificate from the endpoint. If it fails, log an error and exit.</li>
* <li>Show the captured certificate details to the user and ask if they want to add it to the custom truststore.</li>
* <li>If the user agrees, ask for a password for the custom truststore and create/update it with the captured certificate.</li>
* <li>Configure IDE_OPTIONS to use the custom truststore by default.</li>
* <li>Check if the endpoint is now reachable with the custom truststore and log the result.</li>
* </ul>
*/
@Override
protected void doRun() {

String endpointInput = this.url.getValueAsString();
boolean defaultUrlUsed = false;

if (endpointInput == null || endpointInput.isBlank()) {
endpointInput = "https://www.github.com";
defaultUrlUsed = true;
}

TruststoreUtil.TlsEndpoint endpoint;
try {
endpoint = TruststoreUtil.parseTlsEndpoint(endpointInput);
} catch (IllegalArgumentException e) {
throw new CliException("Invalid target URL/host '" + endpointInput + "': " + e.getMessage(), e);
}

String host = endpoint.host();
int port = endpoint.port();
Path customTruststorePath = this.context.getUserHomeIde().resolve("truststore").resolve("truststore.p12");

if (TruststoreUtil.isTruststorePresent(customTruststorePath) && TruststoreUtil.isReachable(host, port, customTruststorePath)) {
IdeLogLevel.SUCCESS.log(LOG, "TLS handshake succeeded with existing custom truststore at {}.", customTruststorePath);
configureIdeOptions(customTruststorePath);
return;
}

if (TruststoreUtil.isReachable(host, port)) {
IdeLogLevel.SUCCESS.log(LOG, "Successfully connected to {}:{} without certificate changes.", host, port);
LOG.info("No truststore update is required for the given address.");
if (defaultUrlUsed) {
LOG.info(
"If the issue still occurs try to call the command again and add the url that is causing the problem to the command: \n ide fix-vpn-tls-problem <url>");
}

return;
}

LOG.info("The given address {}:{} is not reachable/valid without certificate changes. Continuing with certificate capture.", host, port);

X509Certificate certificate;
try {
certificate = TruststoreUtil.fetchServerCertificate(host, port);
} catch (Exception e) {
LOG.error("Failed to capture certificate from {}:{}.", host, port, e);
IdeLogLevel.INTERACTION.log(LOG,
"Please check proxy/VPN and retry. You can also follow: https://github.com/devonfw/IDEasy/blob/main/documentation/proxy-support.adoc#tls-certificate-issues");
return;
}

LOG.info("Captured untrusted certificate:");
LOG.info(TruststoreUtil.describeCertificate(certificate));

boolean addToTruststore = this.context.question("Do you want to add this certificate to the custom truststore at {}?", customTruststorePath);

if (!addToTruststore) {
LOG.info("Skipped truststore update by user choice.");
return;
}

try {
TruststoreUtil.createOrUpdateTruststore(customTruststorePath, certificate, "custom");
IdeLogLevel.SUCCESS.log(LOG, "Custom truststore updated at {}", customTruststorePath);
} catch (Exception e) {
LOG.error("Failed to create or update custom truststore at {}", customTruststorePath, e);
return;
}

configureIdeOptions(customTruststorePath);

if (TruststoreUtil.isReachable(host, port, customTruststorePath)) {
IdeLogLevel.SUCCESS.log(LOG, "TLS handshake succeeded with custom truststore.");
} else {
LOG.warn("TLS handshake still fails even with custom truststore.");
}
}

private void configureIdeOptions(Path customTruststorePath) {
String truststorePath = customTruststorePath.toAbsolutePath().toString();
String truststoreOption = TRUSTSTORE_OPTION_PREFIX + truststorePath;
String truststorePasswordOption = TRUSTSTORE_PASSWORD_OPTION_PREFIX + Arrays.toString(TruststoreUtil.CUSTOM_TRUSTSTORE_PASSWORD);

EnvironmentVariables confVariables = this.context.getVariables().getByType(EnvironmentVariablesType.USER);

if (confVariables == null) {
IdeLogLevel.INTERACTION.log(LOG, "Please configure IDE_OPTIONS manually: {} {}", truststoreOption, truststorePasswordOption);
return;
}

String options = confVariables.getFlat(IDE_OPTIONS);
options = removeOptionWithPrefix(options, TRUSTSTORE_OPTION_PREFIX);
options = removeOptionWithPrefix(options, TRUSTSTORE_PASSWORD_OPTION_PREFIX);
options = appendOption(options, truststoreOption);
options = appendOption(options, truststorePasswordOption);

try {
confVariables.set(IDE_OPTIONS, options, true);
confVariables.save();
// Apply directly for the current process as well.
System.setProperty("javax.net.ssl.trustStore", truststorePath);
System.setProperty("javax.net.ssl.trustStorePassword", Arrays.toString(TruststoreUtil.CUSTOM_TRUSTSTORE_PASSWORD));
IdeLogLevel.SUCCESS.log(LOG, "IDE_OPTIONS configured to use custom truststore by default.");
} catch (UnsupportedOperationException e) {
IdeLogLevel.INTERACTION.log(LOG, "Please configure IDE_OPTIONS manually: {} {}", truststoreOption, truststorePasswordOption);
}
}

private static String removeOptionWithPrefix(String options, String prefix) {
if ((options == null) || options.isBlank()) {
return "";
}
StringBuilder result = new StringBuilder();
String[] tokens = options.trim().split("\\s+");
for (String token : tokens) {
if (!token.startsWith(prefix)) {
if (!result.isEmpty()) {
result.append(' ');
}
result.append(token);
}
}
return result.toString();
}

private static String appendOption(String options, String option) {
if ((options == null) || options.isBlank()) {
return option;
}
return options + " " + option;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.net.ssl.SSLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -29,6 +29,8 @@ public class NetworkStatusImpl implements NetworkStatus {

protected final CachedValue<Throwable> onlineCheck;

private static final String ERROR_TEXT_PKIX = "pkix path building failed";

/**
* @param ideContext the {@link AbstractIdeContext}.
*/
Expand Down Expand Up @@ -113,10 +115,8 @@ public void logStatusMessage() {
LOG.error(message);
LOG.error(error.toString());
}
if (error instanceof SSLException) {
LOG.warn(
"You are having TLS issues. We guess you are forced to use a VPN tool breaking end-to-end encryption causing this effect. As a workaround you can create and configure a truststore as described here:");
IdeLogLevel.INTERACTION.log(LOG, "https://github.com/devonfw/IDEasy/blob/main/documentation/proxy-support.adoc#tls-certificate-issues");
if (isTlsTrustIssue(error)) {
logTruststoreFixHint();
} else {
IdeLogLevel.INTERACTION.log(LOG, "Please check potential proxy settings, ensure you are properly connected to the internet and retry this operation.");
}
Expand All @@ -133,9 +133,40 @@ public <T> T invokeNetworkTask(Callable<T> callable, String uri) {
return callable.call();
} catch (IOException e) {
this.onlineCheck.set(e);
if (isTlsTrustIssue(e)) {
logTruststoreFixHint();
}
throw new IllegalStateException("Network error whilst communicating to " + uri, e);
} catch (Exception e) {
throw new IllegalStateException("Unexpected checked exception whilst communicating to " + uri, e);
}
}

private void logTruststoreFixHint() {

LOG.warn(
"You are having TLS trust issues (PKIX/certificate-path/SSL handshake). As a workaround you can create and configure a truststore via the following command (replace <url> with the failing endpoint):\nide fix-vpn-tls-problem <url>");
IdeLogLevel.INTERACTION.log(LOG, "https://github.com/devonfw/IDEasy/blob/main/documentation/proxy-support.adoc#tls-certificate-issues");
}

boolean isTlsTrustIssue(Throwable throwable) {
Throwable current = throwable;
while (current != null) {
String message = current.getMessage();
if (containsTlsTrustIndicator(message)) {
return true;
}
current = current.getCause();
}
return false;
}

boolean containsTlsTrustIndicator(String text) {
if ((text == null) || text.isBlank()) {
return false;
}
String normalized = text.toLowerCase(Locale.ROOT);
return normalized.contains(ERROR_TEXT_PKIX);
}

}
Loading
Loading