This commit is contained in:
crschnick
2026-05-10 17:59:35 +00:00
parent 07204b3e37
commit 680ce2f4bf
20 changed files with 528 additions and 39 deletions

View File

@@ -9,6 +9,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.SetChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.control.Button;
import javafx.scene.layout.Region;
@@ -21,6 +22,7 @@ import java.util.function.Predicate;
public final class BrowserConnectionListComp extends SimpleRegionBuilder {
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
private static final PseudoClass BUSY = PseudoClass.getPseudoClass("busy");
private final ObservableValue<DataStoreEntry> selected;
private final Predicate<StoreEntryWrapper> applicable;
private final BiConsumer<StoreEntryWrapper, BooleanProperty> action;
@@ -58,6 +60,13 @@ public final class BrowserConnectionListComp extends SimpleRegionBuilder {
&& newValue.equals(s.getWrapper().getEntry()));
});
});
busyEntries.addListener((SetChangeListener<? super StoreSection>) change -> {
PlatformThread.runLaterIfNeeded(() -> {
struc.pseudoClassStateChanged(
BUSY,
change.getSet().contains(s));
});
});
});
};

View File

@@ -0,0 +1,183 @@
package io.xpipe.app.core;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.comp.base.TextAreaComp;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.platform.OptionsBuilder;
import io.xpipe.app.process.OsFileSystem;
import io.xpipe.app.util.DocumentationLink;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.TlsCertificateFormat;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import lombok.SneakyThrows;
import org.apache.commons.io.FilenameUtils;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.cert.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Enumeration;
import java.util.List;
public class AppCertStore {
private class SavingTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return trustManager.getAcceptedIssuers();
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
trustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
trustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
var cause = e.getCause();
var nonTrusted = cause != null && cause.getClass().getName().equals("sun.security.provider.certpath.SunCertPathBuilderException");
if (nonTrusted) {
showTrustDialog(chain[chain.length - 1]);
ErrorEventFactory.preconfigure(ErrorEventFactory.fromThrowable(e)
.expected()
.omit());
throw e;
} else {
throw ErrorEventFactory.expected(e);
}
}
}
}
private final List<X509Certificate> certificates;
private X509TrustManager trustManager;
private final SavingTrustManager savingTrustManager = new SavingTrustManager();
private AppCertStore(List<X509Certificate> certificates) {this.certificates = certificates;}
public void addCertificate(String name, X509Certificate certificate) {
try {
var dir = AppProperties.get().getDataDir().resolve("cacerts");
Files.createDirectories(dir);
var file = dir.resolve(OsFileSystem.ofLocal().makeFileSystemCompatible(name) + ".pem");
var s = convertToPem(certificate);
Files.writeString(file, s);
certificates.add(certificate);
updateTrustManager();
} catch (Exception e) {
ErrorEventFactory.fromThrowable(e).handle();
}
}
public X509TrustManager getCustomTrustManager() {
return savingTrustManager;
}
@SneakyThrows
private void updateTrustManager() {
KeyStore ks = KeyStore.getInstance("JKS");
var caCertsFile = Path.of(System.getProperty("java.home") + "/lib/security/cacerts");
try (FileInputStream fis = new FileInputStream(caCertsFile.toFile())) {
ks.load(fis, null);
}
for (int i = 0; i < certificates.size(); i++) {
ks.setCertificateEntry(i + "", certificates.get(i));
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
trustManager = (X509TrustManager) tmf.getTrustManagers()[0];
}
private void showTrustDialog(X509Certificate certificate) {
var format = TlsCertificateFormat.format(certificate);
var content = new TextAreaComp(new SimpleStringProperty(format))
.applyStructure(structure -> {
structure.getTextArea().setEditable(false);
})
.prefHeight(450);
var name = new SimpleStringProperty();
var options = new OptionsBuilder()
.nameAndDescription("certificateDetails")
.addComp(content)
.nameAndDescription("certificateName")
.addString(name)
.nonNull()
.buildComp()
.prefWidth(650);
var modal = ModalOverlay.of("untrustedCertificateTitle", options);
modal.addButton(ModalButton.cancel());
modal.addButton(new ModalButton("trust", () -> {
ThreadHelper.runAsync(() -> {
addCertificate(name.getValue(), certificate);
});
}, true, true).augment(button -> {
button.disableProperty().bind(name.isNull());
}));
modal.show();
}
private static AppCertStore INSTANCE;
public static AppCertStore get() {
return INSTANCE;
}
public static void init() {
var dir = AppProperties.get().getDataDir().resolve("cacerts");
if (!Files.exists(dir)) {
INSTANCE = new AppCertStore(new ArrayList<>());
INSTANCE.updateTrustManager();
return;
}
var list = new ArrayList<X509Certificate>();
try (var stream = Files.list(dir)) {
var files = stream.toList();
for (Path f : files) {
var cert = parseCertificate(f);
list.add(cert);
}
} catch (Exception e) {
ErrorEventFactory.fromThrowable(e).expected().handle();
}
INSTANCE = new AppCertStore(list);
INSTANCE.updateTrustManager();
}
public static void reset() {
INSTANCE = null;
}
private static X509Certificate parseCertificate(Path file) throws Exception {
var b = Files.readAllBytes(file);
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(b));
}
private static String convertToPem(X509Certificate cert) throws CertificateEncodingException {
String begin = "-----BEGIN CERTIFICATE-----\n";
String end = "\n-----END CERTIFICATE-----\n";
byte[] derCert = cert.getEncoded();
Base64.Encoder encoder = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.UTF_8));
String pemCertPre = encoder.encodeToString(derCert);
String pemCert = begin + pemCertPre + end;
return pemCert;
}
}

View File

@@ -28,6 +28,7 @@ import io.xpipe.app.prefs.WorkspaceManager;
import io.xpipe.app.process.LocalShell;
import io.xpipe.app.pwman.KeePassXcPasswordManager;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.core.AppCertStore;
import io.xpipe.app.storage.DataStorageSyncHandler;
import io.xpipe.app.terminal.TerminalDockHubManager;
import io.xpipe.app.terminal.TerminalLauncherManager;
@@ -64,6 +65,7 @@ public class AppBaseMode extends AppOperationMode {
// if (true) throw new IllegalStateException();
TrackEvent.info("Initializing base mode components ...");
AppCertStore.init();
AppMainWindow.loadingText("checkingLicense");
LicenseProvider.get().init();
AppMainWindow.loadingText("initializingApp");
@@ -229,6 +231,7 @@ public class AppBaseMode extends AppOperationMode {
AppDataLock.unlock();
BlobManager.reset();
FileBridge.reset();
AppCertStore.reset();
AppFileWatcher.reset();
GlobalTimer.reset();
LocalFileTracker.reset();

View File

@@ -95,6 +95,10 @@ public class ShellSession extends Session {
return false;
}
if (secs < 30) {
secs = 30;
}
return shellControl.isInactive(Duration.ofSeconds(secs));
}

View File

@@ -85,6 +85,25 @@ public class ErrorEvent {
public static class ErrorEventBuilder {
public void apply(ErrorEventBuilder other) {
var e = other.build();
if (!e.reportable) {
expected();
}
if (e.terminal) {
term();
}
if (e.link != null) {
link = e.link;
}
if (e.description != null) {
description = e.description;
}
if (e.omitted) {
omit();
}
}
public ErrorEventBuilder documentationLink(DocumentationLink documentationLink) {
return link(documentationLink.getLink());
}
@@ -133,9 +152,5 @@ public class ErrorEvent {
Throwable getThrowable() {
return throwable;
}
String getLink() {
return link;
}
}
}

View File

@@ -7,10 +7,7 @@ import io.xpipe.core.OsType;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import javax.net.ssl.SSLHandshakeException;
public class ErrorEventFactory {
@@ -53,6 +50,12 @@ public class ErrorEventFactory {
}
public static synchronized void preconfigure(ErrorEvent.ErrorEventBuilder event) {
var found = EVENT_BASES.get(event.getThrowable());
if (found != null) {
found.apply(event);
return;
}
EVENT_BASES.put(event.getThrowable(), event);
}
@@ -62,12 +65,17 @@ public class ErrorEventFactory {
b = ErrorEvent.builder().throwable(t);
}
if (t instanceof SSLHandshakeException
|| (t.getClass().getName().equals("sun.security.provider.certpath.SunCertPathBuilderException"))) {
if (b.getLink() == null) {
b.documentationLink(DocumentationLink.TLS_DECRYPTION);
var chain = new ArrayList<Throwable>();
var current = t;
while (current != null) {
chain.addFirst(current);
current = current.getCause();
}
for (Throwable pre : chain) {
var found = EVENT_BASES.get(pre);
if (found != null) {
b.apply(found);
}
b.expected();
}
// Indicates that the session is scheduled to end and new processes won't be started

View File

@@ -20,7 +20,7 @@ public class GuiErrorHandler extends GuiErrorHandlerBase implements ErrorHandler
public void handle(ErrorEvent event) {
log.handle(event);
if (event.isOmitted() && PlatformState.getCurrent() == PlatformState.RUNNING) {
if (event.isOmitted() && PlatformState.getCurrent() != PlatformState.RUNNING) {
return;
}

View File

@@ -283,7 +283,7 @@ public final class AppPrefs {
.valueClass(ShellScript.class)
.log(false)
.build());
final Property<HttpProxy> httpProxy = map(Mapping.builder()
final ObjectProperty<HttpProxy> httpProxy = map(Mapping.builder()
.property(new GlobalObjectProperty<>())
.key("httpProxy")
.valueClass(HttpProxy.class)
@@ -377,7 +377,7 @@ public final class AppPrefs {
final BooleanProperty checkForSecurityUpdates =
mapLocal(new GlobalBooleanProperty(true), "checkForSecurityUpdates", Boolean.class, false);
final BooleanProperty disableHttpsTlsCheck =
mapLocal(new GlobalBooleanProperty(false), "disableHttpsTlsCheck", Boolean.class, true);
mapLocal(new GlobalBooleanProperty(false), "disableHttpsTlsCheck", Boolean.class, false);
final BooleanProperty condenseConnectionDisplay =
mapLocal(new GlobalBooleanProperty(false), "condenseConnectionDisplay", Boolean.class, false);
final BooleanProperty showChildCategoriesInParentCategory =

View File

@@ -15,6 +15,7 @@ import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.app.util.HttpProxy;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
public class HttpProxyCategory extends AppPrefsCategory {
@@ -32,10 +33,23 @@ public class HttpProxyCategory extends AppPrefsCategory {
@Override
protected BaseRegionBuilder<?, ?> create() {
var prefs = AppPrefs.get();
var disableTlsReadOnly = Bindings.createBooleanBinding(() -> {
return prefs.httpProxy.get() != null && prefs.httpProxy.get().isDisableTlsVerification();
}, prefs.httpProxy);
var disableTlsReadOnlyProp = new SimpleObjectProperty<Boolean>();
disableTlsReadOnlyProp.bind(disableTlsReadOnly);
return new OptionsBuilder()
.title("httpProxyConfiguration")
.sub(proxy())
.sub(new OptionsBuilder().pref(prefs.disableHttpsTlsCheck).addToggle(prefs.disableHttpsTlsCheck))
.sub(new OptionsBuilder()
.pref(prefs.disableHttpsTlsCheck)
.addToggle(prefs.disableHttpsTlsCheck)
.hide(prefs.httpProxy.isNotNull())
.nameAndDescription("disableHttpsTlsCheck")
.addToggle(disableTlsReadOnlyProp)
.disable()
.hide(prefs.httpProxy.isNull())
)
.buildComp();
}

View File

@@ -15,6 +15,7 @@ import io.xpipe.app.process.CommandSupport;
import io.xpipe.app.process.LocalShell;
import io.xpipe.app.process.ShellControl;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.HttpProxy;
import io.xpipe.core.FilePath;
import io.xpipe.core.InPlaceSecretValue;
import io.xpipe.core.JacksonMapper;
@@ -181,7 +182,7 @@ public class PassboltPasswordManager implements PasswordManager {
return null;
}
b.addIf(AppPrefs.get().disableHttpsTlsCheck().getValue(), "--tlsSkipVerify")
b.addIf(HttpProxy.disableTlsVerification(), "--tlsSkipVerify")
.add("--serverAddress")
.addLiteral(serverUrl)
.add("--userPassword")

View File

@@ -39,9 +39,7 @@ public interface WarpTerminalType extends ExternalTerminalType, TrackableTermina
@Override
default boolean isRecommended() {
// Right now, opening scripts is broken
// Maybe this will be fixed at some point by Warp
return false;
return true;
}
@Override

View File

@@ -11,7 +11,6 @@ public enum DocumentationLink {
MACOS_SETUP("guide/installation#macos"),
DOUBLE_PROMPT("troubleshoot/two-step-connections"),
LICENSE_ACTIVATION("troubleshoot/license-activation"),
TLS_DECRYPTION("troubleshoot/license-activation#tls-decryption"),
UPDATE_FAIL("troubleshoot/update-fail"),
PRIVACY("legal/privacy-policy"),
EULA("legal/end-user-license-agreement"),

View File

@@ -6,9 +6,10 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.core.AppCertStore;
import lombok.SneakyThrows;
import java.io.IOException;
import java.io.*;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
@@ -17,9 +18,7 @@ import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.*;
public class HttpHelper {
@@ -28,7 +27,7 @@ public class HttpHelper {
var proxy = HttpProxy.getActiveProxy();
return client(
proxy.orElse(null),
AppPrefs.get() != null && AppPrefs.get().disableHttpsTlsCheck().getValue());
HttpProxy.disableTlsVerification());
}
@SneakyThrows
@@ -54,6 +53,13 @@ public class HttpHelper {
};
sslContext.init(null, new TrustManager[] {trustManager}, new SecureRandom());
builder.sslContext(sslContext);
} else {
var certStore = AppCertStore.get();
if (certStore != null) {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{certStore.getCustomTrustManager()}, null);
builder.sslContext(context);
}
}
if (proxy != null) {

View File

@@ -33,6 +33,15 @@ public class HttpProxy {
return Optional.of(current);
}
public static boolean disableTlsVerification() {
var a = getActiveProxy();
if (a.isPresent()) {
return a.get().isDisableTlsVerification();
} else {
return AppPrefs.get() != null && AppPrefs.get().disableHttpsTlsCheck().getValue();
}
}
public static boolean canUseAsProxy(DataStoreEntryRef<DataStore> ref) {
if (!ref.get().getValidity().isUsable()) {
return false;
@@ -52,9 +61,14 @@ public class HttpProxy {
+ port;
}
public boolean hasAuth() {
return user != null && password != null;
}
String host;
int port;
String user;
InPlaceSecretValue password;
boolean socks5;
boolean disableTlsVerification;
}

View File

@@ -0,0 +1,217 @@
package io.xpipe.app.util;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAKey;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
public class TlsCertificateFormat {
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
public static String format(X509Certificate cert) {
var sb = new StringBuilder();
boolean selfSigned = cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal());
sb.append(" Issued To\n");
appendDnFields(sb, cert.getSubjectX500Principal().getName());
sb.append("\n Issued By");
if (selfSigned) {
sb.append(" [!] SELF-SIGNED — not trusted by any CA");
}
sb.append("\n");
if (!selfSigned) {
appendDnFields(sb, cert.getIssuerX500Principal().getName());
}
sb.append("\n Validity\n");
Instant now = Instant.now();
Instant notBefore = cert.getNotBefore().toInstant();
Instant notAfter = cert.getNotAfter().toInstant();
long daysLeft = ChronoUnit.DAYS.between(now, notAfter);
sb.append(" Issued On : ").append(DATE_FMT.format(notBefore)).append("\n");
sb.append(" Expires On : ").append(DATE_FMT.format(notAfter));
if (now.isBefore(notBefore)) {
sb.append(" [!] NOT YET VALID");
} else if (daysLeft < 0) {
sb.append(" [!] EXPIRED ").append(Math.abs(daysLeft)).append(" days ago");
} else if (daysLeft <= 30) {
sb.append(" [!] expires in ").append(daysLeft).append(" days");
} else {
sb.append(" (").append(daysLeft).append(" days remaining)");
}
sb.append("\n");
try {
Collection<List<?>> sans = cert.getSubjectAlternativeNames();
if (sans != null && !sans.isEmpty()) {
List<String> dns = new ArrayList<>();
List<String> ips = new ArrayList<>();
List<String> other = new ArrayList<>();
for (List<?> san : sans) {
int type = (Integer) san.get(0);
String val = String.valueOf(san.get(1));
switch (type) {
case 2 -> dns.add(val);
case 7 -> ips.add(val);
default -> other.add(sanTypeName(type) + ":" + val);
}
}
sb.append("\n Valid For Hostnames\n");
dns.forEach(h -> sb.append(" ").append(h).append("\n"));
ips.forEach(ip -> sb.append(" IP: ").append(ip).append("\n"));
other.forEach(o -> sb.append(" ").append(o).append("\n"));
}
} catch (Exception ignored) {
}
sb.append("\n Fingerprints\n");
try {
byte[] encoded = cert.getEncoded();
sb.append(" SHA-256 : ").append(fingerprint(encoded, "SHA-256")).append("\n");
sb.append(" SHA-1 : ").append(fingerprint(encoded, "SHA-1")).append("\n");
} catch (Exception ignored) {
}
return sb.toString();
}
private static void appendDnFields(StringBuilder sb, String dn) {
Map<String, String> fields = parseDn(dn);
append(sb, "Common Name (CN)", fields.get("CN"));
append(sb, "Organization (O)", fields.get("O"));
append(sb, "Unit (OU) ", fields.get("OU"));
append(sb, "Locality (L) ", fields.get("L"));
append(sb, "State (ST) ", fields.get("ST"));
append(sb, "Country (C) ", fields.get("C"));
}
private static void append(StringBuilder sb, String label, String value) {
if (value != null) {
sb.append(" ").append(label).append(" : ").append(value).append("\n");
}
}
private static Map<String, String> parseDn(String dn) {
Map<String, String> map = new LinkedHashMap<>();
int i = 0;
int len = dn.length();
while (i < len) {
while (i < len && dn.charAt(i) == ' ')
i++;
if (i >= len) {
break;
}
int typeStart = i;
while (i < len && dn.charAt(i) != '=')
i++;
if (i >= len) {
break;
}
String type = dn.substring(typeStart, i).trim().toUpperCase();
i++;
while (i < len && dn.charAt(i) == ' ')
i++;
var value = new StringBuilder();
if (i < len && dn.charAt(i) == '#') {
i++;
while (i < len && isHex(dn.charAt(i))) {
value.append(dn.charAt(i));
i++;
}
map.putIfAbsent(type, "#" + value);
while (i < len && dn.charAt(i) != ',' && dn.charAt(i) != ';')
i++;
if (i < len) {
i++;
}
continue;
}
boolean quoted = i < len && dn.charAt(i) == '"';
if (quoted) {
i++;
}
outer:
while (i < len) {
char c = dn.charAt(i);
if (quoted) {
if (c == '"') {
i++;
break;
}
} else {
switch (c) {
case ',':
case ';':
i++;
break outer;
case '+':
i++;
break outer;
}
}
if (c == '\\' && i + 1 < len) {
i++;
char next = dn.charAt(i);
if (isHex(next) && i + 1 < len && isHex(dn.charAt(i + 1))) {
value.append((char) Integer.parseInt(dn.substring(i, i + 2), 16));
i += 2;
} else {
value.append(next);
i++;
}
continue;
}
value.append(c);
i++;
}
map.putIfAbsent(type, value.toString().trim());
}
return map;
}
private static boolean isHex(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
private static String fingerprint(byte[] encoded, String algorithm) throws Exception {
byte[] digest = MessageDigest.getInstance(algorithm).digest(encoded);
StringBuilder hex = new StringBuilder();
for (int i = 0; i < digest.length; i++) {
if (i > 0) {
hex.append(':');
}
hex.append(String.format("%02X", digest[i]));
}
return hex.toString();
}
private static String sanTypeName(int type) {
return switch (type) {
case 0 -> "otherName";
case 1 -> "email";
case 3 -> "x400Address";
case 4 -> "directoryName";
case 5 -> "ediPartyName";
case 6 -> "URI";
case 8 -> "registeredID";
default -> "type" + type;
};
}
}

View File

@@ -17,6 +17,10 @@
-fx-font-weight: BOLD;
}
.bookmark-list .store-section-mini-comp .item:busy {
-fx-opacity: 0.5;
}
.bookmark-list .store-section-mini-comp:top {
-fx-padding: 0 0 0 2px;
}

View File

@@ -47,6 +47,8 @@ If you are running XPipe in an enterprise environment behind a proxy, you can al
- Make explicit display scale value only accept multiples of 25% to prevent display issues
- Add synchronization when multiple FIDO2 SSH connections are started to prevent failures caused by concurrent security key requests
- Switch vault key generation to argon2 for improved post quantum security of the vault
- Add option to automatically exit background shell sessions after an inactivity period
- Add move and delete actions for batch selections
## Fixes

View File

@@ -246,15 +246,15 @@ public class IdentityChoiceBuilder {
: null;
if (u == null && p == null && i == null) {
return null;
} else {
return IdentityValue.InPlace.builder()
.identityStore(LocalIdentityStore.builder()
.username(u)
.password(p)
.sshIdentity(i)
.build())
.build();
}
return IdentityValue.InPlace.builder()
.identityStore(LocalIdentityStore.builder()
.username(u)
.password(p)
.sshIdentity(i)
.build())
.build();
}
},
identity);

View File

@@ -154,7 +154,7 @@ nixDist=Nix
antigravity=Antigravity
rsa=RSA
ed25519=ED25519
ed25519Sk=ED25519 (FIDO2)
ed25519Sk=ED25519-SK (FIDO2)
westonEditor=Weston Editor
passbolt=Passbolt
yakuake=Yakuake

View File

@@ -2107,8 +2107,10 @@ openSettingsDescription=Navigate to the corresponding settings entry
toggleSizeLock=Toggle size lock
useExternalNetcatForProxies=Use externally installed netcat executable for proxies
useExternalNetcatForProxiesDescription=Use locally installed netcat executable to proxy SSH connections instead of the built-in proxy functionality if possible. This requires ncat to be installed on Windows and nc on other operating systems.
networkProxy.displayName=Network proxy
networkProxy.displayDescription=Configure a proxy server to use as a gateway for connections
#force
networkProxy.displayName=HTTP/SOCKS proxy
#force
networkProxy.displayDescription=Configure an HTTP/SOCKS proxy server to use as a gateway
networkProxyType=Proxy type
networkProxyTypeDescription=The protocol of the proxy server
networkProxyHost=Host
@@ -2173,4 +2175,14 @@ availableShells=Available shells
#context: verb
keepEnabled=Keep enabled
backgroundSessionInactivityTimeout=Background shell inactivity timeout
backgroundSessionInactivityTimeoutDescription=The amount of seconds to close background shell sessions if they were not used.\n\nBy default, XPipe will keep shell connections to remote systems open in the background to reuse them later on and speed up operations. If you want to automatically close those in a timely manner, you can set this option.
#force
backgroundSessionInactivityTimeoutDescription=The amount of seconds to close background shell sessions after, if they were not used.\n\nBy default, XPipe will keep shell connections to remote systems open in the background to reuse them later on and speed up operations. If you want to automatically close those in a timely manner, you can set this option.
disableTlsVerification=Disable TLS verification
disableTlsVerificationDescription=Don't verify the TLS certificate, in case HTTPS traffic is intercepted by the proxy
certificateDetails=Certificate details
certificateDetailsDescription=Check whether this is the correct certificate
certificateName=Name
certificateNameDescription=Give the stored certificate a recognizable name
#context: verb
trust=Trust
untrustedCertificateTitle=Untrusted certificate