mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-04 03:40:32 +00:00
Properly handle mstsc localhost certs [stage]
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
package io.xpipe.app.rdp;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.platform.OptionsBuilder;
|
||||
import io.xpipe.app.prefs.ExternalApplicationType;
|
||||
import io.xpipe.app.process.CommandBuilder;
|
||||
import io.xpipe.app.process.LocalShell;
|
||||
import io.xpipe.app.util.GlobalTimer;
|
||||
import io.xpipe.app.util.RdpConfig;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.SecretValue;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
@@ -17,7 +19,10 @@ import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@JsonTypeName("mstsc")
|
||||
@Value
|
||||
@@ -25,6 +30,16 @@ import java.util.Map;
|
||||
@Builder
|
||||
public class MstscRdpClient implements ExternalApplicationType.PathApplication, ExternalRdpClient {
|
||||
|
||||
@Value
|
||||
@Jacksonized
|
||||
@Builder
|
||||
public static class RegistryCache {
|
||||
String usernameHint;
|
||||
byte[] certHash;
|
||||
}
|
||||
|
||||
private static int launchCounter = 0;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static OptionsBuilder createOptions(Property<MstscRdpClient> property) {
|
||||
var smartSizing = new SimpleObjectProperty<>(property.getValue().isSmartSizing());
|
||||
@@ -43,13 +58,28 @@ public class MstscRdpClient implements ExternalApplicationType.PathApplication,
|
||||
@Override
|
||||
public void launch(RdpLaunchConfig configuration) throws Exception {
|
||||
var adaptedRdpConfig = getAdaptedConfig(configuration);
|
||||
|
||||
prepareLocalhostRegistryCache(configuration);
|
||||
|
||||
var file = writeRdpConfigFile(configuration.getTitle(), adaptedRdpConfig);
|
||||
LocalShell.getShell()
|
||||
.executeSimpleCommand(CommandBuilder.of().add(getExecutable()).addFile(file.toString()));
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
ThreadHelper.sleep(1000);
|
||||
.command(CommandBuilder.of().add(getExecutable()).addFile(file.toString())).execute();
|
||||
|
||||
GlobalTimer.delay(() -> {
|
||||
FileUtils.deleteQuietly(file.toFile());
|
||||
});
|
||||
}, Duration.ofSeconds(1));
|
||||
|
||||
var localhost = configuration.getConfig().get("full address").orElseThrow().getValue().startsWith("localhost");
|
||||
if (localhost) {
|
||||
var counter = ++launchCounter;
|
||||
GlobalTimer.delay(() -> {
|
||||
if (counter != launchCounter) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveLocalhostRegistryCache(configuration.getStoreId());
|
||||
}, Duration.ofSeconds(15));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,6 +109,51 @@ public class MstscRdpClient implements ExternalApplicationType.PathApplication,
|
||||
return adapted;
|
||||
}
|
||||
|
||||
private void saveLocalhostRegistryCache(UUID entry) {
|
||||
var ex = WindowsRegistry.local().keyExists(WindowsRegistry.HKEY_CURRENT_USER, "Software\\Microsoft\\Terminal Server Client\\Servers\\localhost");
|
||||
if (!ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
var user = WindowsRegistry.local().readStringValueIfPresent(WindowsRegistry.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Terminal Server Client\\Servers\\localhost", "UsernameHint").orElse(null);
|
||||
var cert = WindowsRegistry.local().readBinaryValueIfPresent(WindowsRegistry.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Terminal Server Client\\Servers\\localhost", "CertHash").orElse(null);
|
||||
if (user == null && cert == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppCache.update("rdp-" + entry, RegistryCache.builder().usernameHint(user).certHash(cert).build());
|
||||
}
|
||||
|
||||
private Optional<RegistryCache> getLocalhostRegistryCache(UUID entry) {
|
||||
RegistryCache found = AppCache.getNonNull("rdp-" + entry, RegistryCache.class, () -> null);
|
||||
return Optional.ofNullable(found);
|
||||
}
|
||||
|
||||
private void prepareLocalhostRegistryCache(RdpLaunchConfig configuration) {
|
||||
WindowsRegistry.local().deleteKey(WindowsRegistry.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Terminal Server Client\\Servers\\localhost");
|
||||
|
||||
var localhost = configuration.getConfig().get("full address").orElseThrow().getValue().startsWith("localhost");
|
||||
if (localhost) {
|
||||
var found = getLocalhostRegistryCache(configuration.getStoreId());
|
||||
if (found.isPresent()) {
|
||||
var user = found.get().getUsernameHint();
|
||||
if (user != null) {
|
||||
WindowsRegistry.local().setStringValue(WindowsRegistry.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Terminal Server Client\\Servers\\localhost", "UsernameHint", user);
|
||||
}
|
||||
|
||||
var cert = found.get().getCertHash();
|
||||
if (cert != null) {
|
||||
WindowsRegistry.local().setBinaryValue(WindowsRegistry.HKEY_CURRENT_USER,
|
||||
"Software\\Microsoft\\Terminal Server Client\\Servers\\localhost", "CertHash", cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String encrypt(SecretValue password) throws Exception {
|
||||
var ps = LocalShell.getLocalPowershell().orElseThrow();
|
||||
var cmd = ps.command(CommandBuilder.of()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppNames;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.process.ShellDialect;
|
||||
import io.xpipe.app.process.ShellDialects;
|
||||
@@ -46,6 +47,10 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private static boolean canDeobfuscate() {
|
||||
if (AppProperties.get().isDevelopmentEnvironment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We probably can't run .bat scripts in this case
|
||||
if (OsType.ofLocal() == OsType.WINDOWS && ProcessControlProvider.get().getEffectiveLocalDialect() != ShellDialects.CMD) {
|
||||
return false;
|
||||
|
||||
@@ -89,6 +89,64 @@ public abstract class WindowsRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
public void setStringValue(int hkey, String key, String valueName, String value) {
|
||||
if (!isLibrarySupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Advapi32Util.registryCreateKey(hkey(hkey), key);
|
||||
Advapi32Util.registrySetStringValue(hkey(hkey), key, valueName, value);
|
||||
} catch (Win32Exception ignored) {}
|
||||
}
|
||||
|
||||
public void setBinaryValue(int hkey, String key, String valueName, byte[] value) {
|
||||
if (!isLibrarySupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Advapi32Util.registryCreateKey(hkey(hkey), key);
|
||||
Advapi32Util.registrySetBinaryValue(hkey(hkey), key, valueName, value);
|
||||
} catch (Win32Exception ignored) {}
|
||||
}
|
||||
|
||||
public void deleteKey(int hkey, String key) {
|
||||
if (!isLibrarySupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Advapi32Util.registryDeleteKey(hkey(hkey), key);
|
||||
} catch (Win32Exception ignored) {}
|
||||
}
|
||||
|
||||
public void deleteValue(int hkey, String key, String valueName) {
|
||||
if (!isLibrarySupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Advapi32Util.registryDeleteValue(hkey(hkey), key, valueName);
|
||||
} catch (Win32Exception ignored) {}
|
||||
}
|
||||
|
||||
public Optional<byte[]> readBinaryValueIfPresent(int hkey, String key, String valueName) {
|
||||
if (!isLibrarySupported()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!Advapi32Util.registryValueExists(hkey(hkey), key, valueName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(Advapi32Util.registryGetBinaryValue(hkey(hkey), key, valueName));
|
||||
} catch (Win32Exception ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyExists(int hkey, String key) {
|
||||
if (!isLibrarySupported()) {
|
||||
|
||||
Reference in New Issue
Block a user