From fe97142bfec79beb9e9705a0d2dbd5da08e2835b Mon Sep 17 00:00:00 2001
From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com>
Date: Wed, 23 Oct 2024 06:15:54 +0200
Subject: [PATCH] Nitro http proxy rewrite to proxyee progress
---
.gitignore | 4 +-
G-Earth/pom.xml | 32 ++-
G-Earth/src/main/java/gearth/GEarth.java | 4 +-
.../proxy/nitro/NitroConstants.java | 18 ++
.../proxy/nitro/NitroProxyProvider.java | 10 +-
.../proxy/nitro/http/NitroAuthority.java | 23 +-
.../nitro/http/NitroCertificateFactory.java | 114 ++++++++++
.../http/NitroCertificateSniffingManager.java | 118 ----------
.../proxy/nitro/http/NitroHttpProxy.java | 104 ++++++---
.../nitro/http/NitroHttpProxyFilter.java | 203 ------------------
.../http/NitroHttpProxyFilterSource.java | 47 ----
.../nitro/http/NitroHttpProxyIntercept.java | 35 +++
.../nitro/http/NitroSslContextFactory.java | 8 +-
.../proxy/nitro/os/macos/NitroMacOS.java | 2 +-
.../proxy/nitro/os/windows/NitroWindows.java | 42 ++--
.../nitro/websocket/NitroWebsocketProxy.java | 10 +-
pom.xml | 2 +-
17 files changed, 311 insertions(+), 465 deletions(-)
create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateFactory.java
delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java
delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java
delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java
create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyIntercept.java
diff --git a/.gitignore b/.gitignore
index a2e2560..85bd273 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,6 @@ Extensions/BlockReplacePackets/.classpath
# Certificates
*.p12
-*.pem
\ No newline at end of file
+*.pem
+*.crt
+*.key
\ No newline at end of file
diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml
index 79c31b4..13dc8cf 100644
--- a/G-Earth/pom.xml
+++ b/G-Earth/pom.xml
@@ -313,20 +313,38 @@
1.5.0
- com.github.ganskef
- littleproxy-mitm
- 1.1.0
+ com.github.monkeywie
+ proxyee
+ 1.7.6
- org.slf4j
- slf4j-api
+ org.bouncycastle
+ bcprov-jdk15on
- org.slf4j
- slf4j-log4j12
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+ 1.78.1
+
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+ 1.78.1
+
+
+
+ org.bouncycastle
+ bctls-jdk18on
+ 1.78.1
+
G-Earth
G-Wasm-Minimal
diff --git a/G-Earth/src/main/java/gearth/GEarth.java b/G-Earth/src/main/java/gearth/GEarth.java
index a9eccc0..e04c271 100644
--- a/G-Earth/src/main/java/gearth/GEarth.java
+++ b/G-Earth/src/main/java/gearth/GEarth.java
@@ -122,8 +122,8 @@ public class GEarth extends Application {
primaryStage.show();
primaryStage.setOnCloseRequest(event -> closeGEarth());
- AdminValidator.validate();
- UpdateChecker.checkForUpdates();
+ //AdminValidator.validate();
+ //UpdateChecker.checkForUpdates();
}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java
index a6b8fd7..8a7d095 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java
@@ -9,4 +9,22 @@ public final class NitroConstants {
public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768";
public static final String WEBSOCKET_CLIENT_IDENTIFIER = "HTML5";
+ public static final String[] CIPHER_SUITES = new String[] {
+ "TLS_AES_128_GCM_SHA256",
+ "TLS_AES_256_GCM_SHA384",
+ "TLS_CHACHA20_POLY1305_SHA256",
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
+ "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
+ "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ "TLS_RSA_WITH_AES_256_GCM_SHA384",
+ "TLS_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_RSA_WITH_AES_256_CBC_SHA"
+ };
+
}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java
index 94d6869..90ea41a 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java
@@ -6,8 +6,7 @@ import gearth.protocol.connection.HProxySetter;
import gearth.protocol.connection.HState;
import gearth.protocol.connection.HStateSetter;
import gearth.protocol.connection.proxy.ProxyProvider;
-import gearth.protocol.connection.proxy.nitro.http.NitroAuthority;
-import gearth.protocol.connection.proxy.nitro.http.NitroCertificateSniffingManager;
+import gearth.protocol.connection.proxy.nitro.http.NitroCertificateFactory;
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy;
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback;
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
@@ -33,8 +32,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
private String originalCookies;
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
- final NitroAuthority authority = new NitroAuthority();
- final NitroCertificateSniffingManager certificateManager = new NitroCertificateSniffingManager(authority);
+ final NitroCertificateFactory certificateManager = new NitroCertificateFactory();
this.proxySetter = proxySetter;
this.stateSetter = stateSetter;
@@ -103,7 +101,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
try {
nitroHttpProxy.stop();
} catch (Exception e) {
- e.printStackTrace();
+ logger.error("Failed to stop nitro http proxy", e);
}
logger.info("Stopping nitro websocket proxy");
@@ -111,7 +109,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
try {
nitroWebsocketProxy.stop();
} catch (Exception e) {
- e.printStackTrace();
+ logger.error("Failed to stop nitro websocket proxy", e);
}
stateSetter.setState(HState.NOT_CONNECTED);
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java
index fd5bd4e..5e6d25f 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java
@@ -1,24 +1,9 @@
package gearth.protocol.connection.proxy.nitro.http;
-import org.littleshoot.proxy.mitm.Authority;
+public class NitroAuthority {
-import java.io.File;
-
-public class NitroAuthority extends Authority {
-
- private static final String CERT_ALIAS = "gearth-nitro";
- private static final String CERT_ORGANIZATION = "G-Earth Nitro";
- private static final String CERT_DESCRIPTION = "G-Earth nitro support";
-
- public NitroAuthority() {
- super(new File("."),
- CERT_ALIAS,
- "verysecure".toCharArray(),
- CERT_DESCRIPTION,
- CERT_ORGANIZATION,
- "Certificate Authority",
- CERT_ORGANIZATION,
- CERT_DESCRIPTION);
- }
+ public static final String CERT_ALIAS = "gearth-nitro-v2";
+ public static final String CERT_ORGANIZATION = "G-Earth Nitro";
+ public static final String CERT_DESCRIPTION = "G-Earth nitro support v2";
}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateFactory.java
new file mode 100644
index 0000000..c18604c
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateFactory.java
@@ -0,0 +1,114 @@
+package gearth.protocol.connection.proxy.nitro.http;
+
+import com.github.monkeywie.proxyee.crt.CertUtil;
+import com.github.monkeywie.proxyee.server.HttpProxyCACertFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLEngine;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public class NitroCertificateFactory implements HttpProxyCACertFactory {
+
+ private static final Logger log = LoggerFactory.getLogger(NitroCertificateFactory.class);
+
+ private final File caCertFile;
+ private final File caKeyFile;
+
+ private X509Certificate caCert;
+ private PrivateKey caKey;
+
+ public NitroCertificateFactory() {
+ this.caCertFile = new File(String.format("./%s.crt", NitroAuthority.CERT_ALIAS));
+ this.caKeyFile = new File(String.format("./%s.key", NitroAuthority.CERT_ALIAS));
+ }
+
+ public File getCaCertFile() {
+ return caCertFile;
+ }
+
+ public File getCaKeyFile() {
+ return caKeyFile;
+ }
+
+ public boolean loadOrCreate() {
+ if (this.caCertFile.exists() && this.caKeyFile.exists()) {
+ return this.loadCertificate();
+ }
+
+ // Delete any existing files
+ if (caCertFile.exists()) {
+ caCertFile.delete();
+ }
+
+ if (caKeyFile.exists()) {
+ caKeyFile.delete();
+ }
+
+ // Create the certificate and key files
+ return this.createCertificate();
+ }
+
+ private boolean createCertificate() {
+ try {
+ final KeyPair keyPair = CertUtil.genKeyPair();
+
+ final String subject = String.format("O=%s, OU=Certificate Authority, CN=%s",
+ NitroAuthority.CERT_ORGANIZATION,
+ NitroAuthority.CERT_DESCRIPTION);
+
+ final X509Certificate rootCertificate = CertUtil.genCACert(subject,
+ new Date(),
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(3650)),
+ keyPair);
+
+ this.caCert = rootCertificate;
+ this.caKey = keyPair.getPrivate();
+
+ Files.write(Paths.get(this.caCertFile.toURI()), this.caCert.getEncoded());
+ Files.write(Paths.get(this.caKeyFile.toURI()), new PKCS8EncodedKeySpec(this.caKey.getEncoded()).getEncoded());
+
+ return true;
+ } catch (Exception e) {
+ log.error("Failed to create root certificate", e);
+ }
+
+ return false;
+ }
+
+ private boolean loadCertificate() {
+ try {
+ this.caCert = CertUtil.loadCert(this.caCertFile.toURI());
+ this.caKey = CertUtil.loadPriKey(this.caKeyFile.toURI());
+
+ return true;
+ } catch (Exception e) {
+ log.error("Failed to load root certificate", e);
+ }
+
+ return false;
+ }
+
+ @Override
+ public X509Certificate getCACert() {
+ return this.caCert;
+ }
+
+ @Override
+ public PrivateKey getCAPriKey() {
+ return this.caKey;
+ }
+
+ public SSLEngine websocketSslEngine(String host) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java
deleted file mode 100644
index c5c3d63..0000000
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package gearth.protocol.connection.proxy.nitro.http;
-
-import io.netty.handler.codec.http.HttpRequest;
-import org.littleshoot.proxy.MitmManager;
-import org.littleshoot.proxy.mitm.*;
-
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.List;
-
-/**
- * {@link MitmManager} that uses the common name and subject alternative names
- * from the upstream certificate to create a dynamic certificate with it.
- */
-public class NitroCertificateSniffingManager implements MitmManager {
-
- private static final boolean DEBUG = false;
-
- private final BouncyCastleSslEngineSource sslEngineSource;
- private final Authority authority;
-
- public NitroCertificateSniffingManager(Authority authority) {
- this.authority = authority;
- try {
- sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true, null);
- } catch (final Exception e) {
- throw new RuntimeException(new RootCertificateException("Errors during assembling root CA.", e));
- }
- }
-
- public Authority getAuthority() {
- return authority;
- }
-
- public SSLEngine websocketSslEngine(String commonName) {
- final SubjectAlternativeNameHolder san = new SubjectAlternativeNameHolder();
-
- san.addDomainName("localhost");
- san.addIpAddress("127.0.0.1");
-
- try {
- return sslEngineSource.createCertForHost(commonName, san);
- } catch (Exception e) {
- throw new FakeCertificateException("Failed to create WebSocket certificate", e);
- }
- }
-
- public SSLEngine serverSslEngine(String peerHost, int peerPort) {
- return sslEngineSource.newSslEngine(peerHost, peerPort);
- }
-
- public SSLEngine serverSslEngine() {
- return sslEngineSource.newSslEngine();
- }
-
- public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession) {
- try {
- X509Certificate upstreamCert = getCertificateFromSession(serverSslSession);
- // TODO store the upstream cert by commonName to review it later
-
- // A reasons to not use the common name and the alternative names
- // from upstream certificate from serverSslSession to create the
- // dynamic certificate:
- //
- // It's not necessary. The host name is accepted by the browser.
- //
- String commonName = getCommonName(upstreamCert);
-
- SubjectAlternativeNameHolder san = new SubjectAlternativeNameHolder();
-
- san.addAll(upstreamCert.getSubjectAlternativeNames());
-
- if (DEBUG) {
- System.out.println("[NitroCertificateSniffingManager] Subject Alternative Names");
-
- for (List> name : upstreamCert.getSubjectAlternativeNames()) {
- System.out.printf("[NitroCertificateSniffingManager] - %s%n", name.toString());
- }
- }
-
- return sslEngineSource.createCertForHost(commonName, san);
- } catch (Exception e) {
- throw new FakeCertificateException("Creation dynamic certificate failed", e);
- }
- }
-
- private X509Certificate getCertificateFromSession(SSLSession sslSession) throws SSLPeerUnverifiedException {
- Certificate[] peerCerts = sslSession.getPeerCertificates();
- Certificate peerCert = peerCerts[0];
- if (peerCert instanceof java.security.cert.X509Certificate) {
- return (java.security.cert.X509Certificate) peerCert;
- }
- throw new IllegalStateException("Required java.security.cert.X509Certificate, found: " + peerCert);
- }
-
- private String getCommonName(X509Certificate c) {
- if (DEBUG) {
- System.out.printf("[NitroCertificateSniffingManager] Subject DN principal name: %s%n", c.getSubjectDN().getName());
- }
-
- for (String each : c.getSubjectDN().getName().split(",\\s*")) {
- if (each.startsWith("CN=")) {
- String result = each.substring(3);
-
- if (DEBUG) {
- System.out.printf("[NitroCertificateSniffingManager] Common Name: %s%n", c.getSubjectDN().getName());
- }
-
- return result;
- }
- }
-
- throw new IllegalStateException("Missed CN in Subject DN: " + c.getSubjectDN());
- }
-}
\ No newline at end of file
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java
index cb7e0ba..7afc683 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java
@@ -1,42 +1,57 @@
package gearth.protocol.connection.proxy.nitro.http;
+import com.github.monkeywie.proxyee.server.HttpProxyServer;
+import com.github.monkeywie.proxyee.server.HttpProxyServerConfig;
import gearth.misc.ConfirmationDialog;
import gearth.protocol.connection.proxy.nitro.NitroConstants;
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory;
import gearth.ui.titlebar.TitleBarController;
import gearth.ui.translations.LanguageBundle;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
-import org.littleshoot.proxy.HttpProxyServer;
-import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.net.ssl.SSLException;
import java.io.File;
-import java.io.IOException;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
public class NitroHttpProxy {
+ private static final Logger log = LoggerFactory.getLogger(NitroHttpProxy.class);
+
private static final String ADMIN_WARNING_KEY = "admin_warning_dialog";
private static final AtomicBoolean SHUTDOWN_HOOK = new AtomicBoolean();
private final NitroOsFunctions osFunctions;
private final NitroHttpProxyServerCallback serverCallback;
- private final NitroCertificateSniffingManager certificateManager;
+ private final NitroCertificateFactory certificateFactory;
private HttpProxyServer proxyServer = null;
- public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback, NitroCertificateSniffingManager certificateManager) {
+ public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback, NitroCertificateFactory certificateManager) {
this.serverCallback = serverCallback;
- this.certificateManager = certificateManager;
+ this.certificateFactory = certificateManager;
this.osFunctions = NitroOsFunctionsFactory.create();
}
private boolean initializeCertificate() {
- final File certificate = this.certificateManager.getAuthority().aliasFile(".pem");
+ if (!this.certificateFactory.loadOrCreate()) {
+ return false;
+ }
+
+ final File certificate = this.certificateFactory.getCaCertFile();
// All good if certificate is already trusted.
if (this.osFunctions.isRootCertificateTrusted(certificate)) {
@@ -48,28 +63,31 @@ public class NitroHttpProxy {
final AtomicBoolean shouldInstall = new AtomicBoolean();
Platform.runLater(() -> {
- Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY,
- LanguageBundle.get("alert.rootcertificate.title"), null,
- "", LanguageBundle.get("alert.rootcertificate.remember"),
- ButtonType.YES, ButtonType.NO
- );
-
- alert.getDialogPane().setContent(new Label(LanguageBundle.get("alert.rootcertificate.content").replaceAll("\\\\n", System.lineSeparator())));
-
try {
+ Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY,
+ LanguageBundle.get("alert.rootcertificate.title"), null,
+ "", LanguageBundle.get("alert.rootcertificate.remember"),
+ ButtonType.YES, ButtonType.NO
+ );
+
+ alert.getDialogPane().setContent(new Label(LanguageBundle.get("alert.rootcertificate.content").replaceAll("\\\\n", System.lineSeparator())));
+
+ log.debug("Showing certificate install dialog");
+
shouldInstall.set(TitleBarController.create(alert).showAlertAndWait()
.filter(t -> t == ButtonType.YES).isPresent());
- } catch (IOException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ log.error("Failed to show install dialog", e);
+ } finally {
+ waitForDialog.release();
}
- waitForDialog.release();
});
// Wait for dialog choice.
try {
waitForDialog.acquire();
} catch (InterruptedException e) {
- e.printStackTrace();
+ log.error("Interrupted while waiting for user input", e);
return false;
}
@@ -78,7 +96,7 @@ public class NitroHttpProxy {
return false;
}
- return this.osFunctions.installRootCertificate(this.certificateManager.getAuthority().aliasFile(".pem"));
+ return this.osFunctions.installRootCertificate(this.certificateFactory.getCaCertFile());
}
/**
@@ -98,24 +116,44 @@ public class NitroHttpProxy {
public boolean start() {
setupShutdownHook();
- proxyServer = DefaultHttpProxyServer.bootstrap()
- .withPort(NitroConstants.HTTP_PORT)
- .withManInTheMiddle(this.certificateManager)
- .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback))
- .withTransparent(true)
- .start();
-
if (!initializeCertificate()) {
- proxyServer.stop();
+ log.error("Failed to initialize certificate");
+ return false;
+ }
- System.out.println("Failed to initialize certificate");
+ final HttpProxyServerConfig config = new HttpProxyServerConfig();
+
+ config.setHandleSsl(true);
+
+ proxyServer = new HttpProxyServer()
+ .serverConfig(config)
+ .caCertFactory(this.certificateFactory)
+ .proxyInterceptInitializer(new NitroHttpProxyIntercept(serverCallback));
+
+ proxyServer.startAsync(NitroConstants.HTTP_PORT);
+
+ // Hack to swap the SSL context.
+ try {
+ Security.addProvider(new BouncyCastleProvider());
+
+ config.setClientSslCtx(SslContextBuilder
+ .forClient()
+ .sslContextProvider(new BouncyCastleJsseProvider())
+ .trustManager(InsecureTrustManagerFactory.INSTANCE)
+ .protocols("TLSv1.3", "TLSv1.2")
+ .ciphers(new HashSet<>(Arrays.asList(NitroConstants.CIPHER_SUITES)))
+ .build());
+ } catch (SSLException e) {
+ proxyServer.close();
+
+ log.error("Failed to create nitro proxy SSL context", e);
return false;
}
if (!registerProxy()) {
- proxyServer.stop();
+ proxyServer.close();
- System.out.println("Failed to register certificate");
+ log.error("Failed to register system proxy");
return false;
}
@@ -124,7 +162,7 @@ public class NitroHttpProxy {
public void pause() {
if (!unregisterProxy()) {
- System.out.println("Failed to unregister system proxy, please check manually");
+ log.error("Failed to unregister system proxy, please check manually");
}
}
@@ -135,7 +173,7 @@ public class NitroHttpProxy {
return;
}
- proxyServer.stop();
+ proxyServer.close();
proxyServer = null;
}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java
deleted file mode 100644
index 5d9978d..0000000
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package gearth.protocol.connection.proxy.nitro.http;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.*;
-import io.netty.util.CharsetUtil;
-import org.littleshoot.proxy.HttpFiltersAdapter;
-
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class NitroHttpProxyFilter extends HttpFiltersAdapter {
-
- private static final String NitroConfigSearch = "socket.url";
- private static final String NitroClientSearch = "configurationUrls:";
- private static final Pattern NitroConfigPattern = Pattern.compile("[\"']socket\\.url[\"']:(\\s+)?[\"'](wss?:.*?)[\"']", Pattern.MULTILINE);
-
- // https://developers.cloudflare.com/fundamentals/get-started/reference/cloudflare-cookies/
- private static final HashSet CloudflareCookies = new HashSet<>(Arrays.asList(
- "__cflb",
- "__cf_bm",
- "__cfseq",
- "cf_ob_info",
- "cf_use_ob",
- "__cfwaitingroom",
- "__cfruid",
- "_cfuvid",
- "cf_clearance",
- "cf_chl_rc_i",
- "cf_chl_rc_ni",
- "cf_chl_rc_m"
- ));
-
- private static final String HeaderAcceptEncoding = "Accept-Encoding";
- private static final String HeaderAge = "Age";
- private static final String HeaderCacheControl = "Cache-Control";
- private static final String HeaderContentSecurityPolicy = "Content-Security-Policy";
- private static final String HeaderETag = "ETag";
- private static final String HeaderIfNoneMatch = "If-None-Match";
- private static final String HeaderIfModifiedSince = "If-Modified-Since";
- private static final String HeaderLastModified = "Last-Modified";
-
- private final NitroHttpProxyServerCallback callback;
- private final String url;
- private String cookies;
-
- public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) {
- super(originalRequest, ctx);
- this.callback = callback;
- this.url = url;
- }
-
- @Override
- public HttpResponse clientToProxyRequest(HttpObject httpObject) {
- if (httpObject instanceof HttpRequest) {
- HttpRequest request = (HttpRequest) httpObject;
- HttpHeaders headers = request.headers();
-
- // Only support gzip or deflate.
- // The LittleProxy library does not support brotli.
- if (headers.contains(HeaderAcceptEncoding)) {
- String encoding = headers.get(HeaderAcceptEncoding);
-
- if (encoding.contains("br")) {
- if (encoding.contains("gzip") && encoding.contains("deflate")) {
- headers.set(HeaderAcceptEncoding, "gzip, deflate");
- } else if (encoding.contains("gzip")) {
- headers.set(HeaderAcceptEncoding, "gzip, deflate");
- } else {
- headers.remove(HeaderAcceptEncoding);
- }
- }
- }
-
- // Disable caching.
- stripCacheHeaders(headers);
-
- // Find relevant cookies for the WebSocket connection.
- this.cookies = parseCookies(request);
- }
-
- return super.clientToProxyRequest(httpObject);
- }
-
- @Override
- public HttpObject serverToProxyResponse(HttpObject httpObject) {
- if (httpObject instanceof FullHttpResponse) {
- final FullHttpResponse response = (FullHttpResponse) httpObject;
-
- // Find nitro configuration file.
- boolean responseModified = false;
- String responseBody = responseRead(response);
-
- if (responseBody.contains(NitroConfigSearch)) {
- final Matcher matcher = NitroConfigPattern.matcher(responseBody);
-
- if (matcher.find()) {
- final String originalWebsocket = matcher.group(2).replace("\\/", "/");
- final String replacementWebsocket = callback.replaceWebsocketServer(this.url, originalWebsocket);
-
- if (replacementWebsocket != null) {
- responseBody = responseBody.replace(matcher.group(2), replacementWebsocket);
- responseModified = true;
- }
- }
-
- callback.setOriginCookies(this.cookies);
- }
-
- // Apply changes.
- if (responseModified) {
- responseWrite(response, responseBody);
- }
-
- // CSP.
- if (responseBody.contains(NitroClientSearch)) {
- stripContentSecurityPolicy(response);
- }
- }
-
- return httpObject;
- }
-
- /**
- * Check if cookies from the request need to be recorded for the websocket connection to the origin server.
- */
- private String parseCookies(final HttpRequest request) {
- final List result = new ArrayList<>();
- final List cookieHeaders = request.headers().getAll("Cookie");
-
- for (final String cookieHeader : cookieHeaders) {
- final String[] cookies = cookieHeader.split(";");
-
- for (final String cookie : cookies) {
- final String[] parts = cookie.trim().split("=");
-
- if (CloudflareCookies.contains(parts[0])) {
- result.add(cookie.trim());
- }
- }
- }
-
- if (result.size() == 0) {
- return null;
- }
-
- return String.join("; ", result);
- }
-
- /**
- * Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth.
- */
- private void stripContentSecurityPolicy(FullHttpResponse response) {
- final HttpHeaders headers = response.headers();
-
- if (!headers.contains(HeaderContentSecurityPolicy)){
- return;
- }
-
- String csp = headers.get(HeaderContentSecurityPolicy);
-
- if (csp.contains("connect-src")) {
- csp = csp.replace("connect-src", "connect-src *");
- } else if (csp.contains("default-src")) {
- csp = csp.replace("default-src", "default-src *");
- }
-
- headers.set(HeaderContentSecurityPolicy, csp);
- }
-
- private static String responseRead(FullHttpResponse response) {
- final ByteBuf contentBuf = response.content();
- return contentBuf.toString(CharsetUtil.UTF_8);
- }
-
- private static void responseWrite(FullHttpResponse response, String content) {
- final byte[] body = content.getBytes(StandardCharsets.UTF_8);
-
- // Update content.
- response.content().clear().writeBytes(body);
-
- // Update content-length.
- HttpHeaders.setContentLength(response, body.length);
-
- // Ensure modified response is not cached.
- stripCacheHeaders(response.headers());
- }
-
- private static void stripCacheHeaders(HttpHeaders headers) {
- headers.remove(HeaderAcceptEncoding);
- headers.remove(HeaderAge);
- headers.remove(HeaderCacheControl);
- headers.remove(HeaderETag);
- headers.remove(HeaderIfNoneMatch);
- headers.remove(HeaderIfModifiedSince);
- headers.remove(HeaderLastModified);
- }
-}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java
deleted file mode 100644
index c736458..0000000
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package gearth.protocol.connection.proxy.nitro.http;
-
-import gearth.protocol.connection.proxy.nitro.NitroConstants;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.util.AttributeKey;
-import org.littleshoot.proxy.HttpFilters;
-import org.littleshoot.proxy.HttpFiltersAdapter;
-import org.littleshoot.proxy.HttpFiltersSourceAdapter;
-
-public class NitroHttpProxyFilterSource extends HttpFiltersSourceAdapter {
-
- private static final AttributeKey CONNECTED_URL = AttributeKey.valueOf("connected_url");
-
- private final NitroHttpProxyServerCallback callback;
-
- public NitroHttpProxyFilterSource(NitroHttpProxyServerCallback callback) {
- this.callback = callback;
- }
-
- @Override
- public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
- // https://github.com/ganskef/LittleProxy-mitm#resolving-uri-in-case-of-https
- String uri = originalRequest.getUri();
- if (originalRequest.getMethod() == HttpMethod.CONNECT) {
- if (ctx != null) {
- String prefix = "https://" + uri.replaceFirst(":443$", "");
- ctx.channel().attr(CONNECTED_URL).set(prefix);
- }
- return new HttpFiltersAdapter(originalRequest, ctx);
- }
-
- String connectedUrl = ctx.channel().attr(CONNECTED_URL).get();
- if (connectedUrl == null) {
- return new NitroHttpProxyFilter(originalRequest, ctx, callback, uri);
- }
-
- return new NitroHttpProxyFilter(originalRequest, ctx, callback, connectedUrl + uri);
- }
-
- @Override
- public int getMaximumResponseBufferSizeInBytes() {
- // Increasing this causes LittleProxy to output "FullHttpResponse" objects.
- return NitroConstants.HTTP_BUFFER_SIZE;
- }
-}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyIntercept.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyIntercept.java
new file mode 100644
index 0000000..81273e3
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyIntercept.java
@@ -0,0 +1,35 @@
+package gearth.protocol.connection.proxy.nitro.http;
+
+import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptInitializer;
+import com.github.monkeywie.proxyee.intercept.HttpProxyInterceptPipeline;
+import com.github.monkeywie.proxyee.intercept.common.FullResponseIntercept;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NitroHttpProxyIntercept extends HttpProxyInterceptInitializer {
+
+ private static final Logger log = LoggerFactory.getLogger(NitroHttpProxyIntercept.class);
+
+ public NitroHttpProxyIntercept(NitroHttpProxyServerCallback serverCallback) {
+
+ }
+
+ @Override
+ public void init(HttpProxyInterceptPipeline pipeline) {
+ pipeline.addLast(new FullResponseIntercept() {
+ @Override
+ public boolean match(HttpRequest httpRequest, HttpResponse httpResponse, HttpProxyInterceptPipeline httpProxyInterceptPipeline) {
+ log.debug("Intercepting response for {}", httpRequest.uri());
+ return false;
+ }
+
+ @Override
+ public void handleResponse(HttpRequest httpRequest, FullHttpResponse httpResponse, HttpProxyInterceptPipeline pipeline) {
+ super.handleResponse(httpRequest, httpResponse, pipeline);
+ }
+ });
+ }
+}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroSslContextFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroSslContextFactory.java
index 9e12b69..fd24029 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroSslContextFactory.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroSslContextFactory.java
@@ -6,15 +6,15 @@ import javax.net.ssl.SSLEngine;
public class NitroSslContextFactory extends SslContextFactory.Server {
- private final NitroCertificateSniffingManager certificateManager;
+ private final NitroCertificateFactory certificateFactory;
- public NitroSslContextFactory(NitroCertificateSniffingManager certificateManager) {
- this.certificateManager = certificateManager;
+ public NitroSslContextFactory(NitroCertificateFactory certificateFactory) {
+ this.certificateFactory = certificateFactory;
}
@Override
public SSLEngine newSSLEngine(String host, int port) {
System.out.printf("[NitroSslContextFactory] Creating SSLEngine for %s:%d%n", host, port);
- return certificateManager.websocketSslEngine(host);
+ return certificateFactory.websocketSslEngine(host);
}
}
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/macos/NitroMacOS.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/macos/NitroMacOS.java
index 35d5ff1..436cf7e 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/macos/NitroMacOS.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/macos/NitroMacOS.java
@@ -11,7 +11,7 @@ public class NitroMacOS implements NitroOsFunctions {
/**
* Semicolon separated hosts to ignore for proxying.
*/
- private static final String PROXY_IGNORE = "discord.com;discordapp.com;github.com;";
+ private static final String PROXY_IGNORE = "discord.com;discordapp.com;canary.discord.com;canary.discordapp.com;github.com;";
/**
* Checks if the certificate is trusted by the local machine.
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java
index fb7d370..682119f 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java
@@ -1,21 +1,21 @@
package gearth.protocol.connection.proxy.nitro.os.windows;
-import com.sun.jna.platform.win32.Kernel32;
-import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinDef;
-import com.sun.jna.ptr.IntByReference;
import gearth.misc.RuntimeUtil;
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
public class NitroWindows implements NitroOsFunctions {
+ private static final Logger log = LoggerFactory.getLogger(NitroWindows.class);
+
/**
* Semicolon separated hosts to ignore for proxying.
*/
- // habba.io;
private static final String PROXY_IGNORE = "discord.com;discordapp.com;github.com;challenges.cloudflare.com;";
/**
@@ -31,7 +31,7 @@ public class NitroWindows implements NitroOsFunctions {
return !output.contains("CERT_TRUST_IS_UNTRUSTED_ROOT") &&
output.contains("dwInfoStatus=10c dwErrorStatus=0");
} catch (IOException e) {
- e.printStackTrace();
+ log.error("Failed to check if root certificate is trusted", e);
}
return false;
@@ -39,21 +39,27 @@ public class NitroWindows implements NitroOsFunctions {
@Override
public boolean installRootCertificate(File certificate) {
- final String certificatePath = certificate.getAbsolutePath();
+ final String certificatePath = certificate.toPath().normalize().toAbsolutePath().toString();
- // Prompt UAC elevation.
- WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(null, "runas", "cmd.exe", "/S /C 'certutil -addstore root \"" + certificatePath + "\"'", null, 1);
+ // Correct the command for certutil
+ final String installCommand = "/c certutil -addstore root \"" + certificatePath + "\"";
- // Wait for exit.
- Kernel32.INSTANCE.WaitForSingleObject(result, WinBase.INFINITE);
+ log.debug("Installing root certificate with command: {}", installCommand);
- // Exit code for certutil.
- final IntByReference statusRef = new IntByReference(-1);
- Kernel32.INSTANCE.GetExitCodeProcess(result, statusRef);
+ // Prompt UAC elevation using ShellExecuteA with "runas"
+ WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(
+ null, // Handle to parent window (optional)
+ "runas", // Use "runas" to request elevation
+ "cmd.exe", // Program to execute
+ installCommand, // Command to run with cmd.exe /c
+ null, // Directory (optional)
+ 1 // Show the window
+ );
- // Check if process exited without errors
- if (statusRef.getValue() != -1) {
- System.out.printf("Certutil command exited with exit code %s%n", statusRef.getValue());
+ final int resultValue = result.toNative().hashCode();
+
+ if (resultValue <= 32) { // If the result is <= 32, an error occurred
+ log.error("Failed to start process for installing root certificate. Error code: {}", resultValue);
return false;
}
@@ -69,7 +75,7 @@ public class NitroWindows implements NitroOsFunctions {
Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f");
return true;
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Failed to register system proxy", e);
}
return false;
@@ -81,7 +87,7 @@ public class NitroWindows implements NitroOsFunctions {
Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 0 /f");
return true;
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Failed to unregister system proxy", e);
}
return false;
diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java
index a21244c..5149390 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java
@@ -4,7 +4,7 @@ import gearth.protocol.HConnection;
import gearth.protocol.connection.HProxySetter;
import gearth.protocol.connection.HStateSetter;
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
-import gearth.protocol.connection.proxy.nitro.http.NitroCertificateSniffingManager;
+import gearth.protocol.connection.proxy.nitro.http.NitroCertificateFactory;
import gearth.protocol.connection.proxy.nitro.http.NitroSslContextFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@@ -22,7 +22,7 @@ public class NitroWebsocketProxy {
private final HStateSetter stateSetter;
private final HConnection connection;
private final NitroProxyProvider proxyProvider;
- private final NitroCertificateSniffingManager certificateManager;
+ private final NitroCertificateFactory certificateFactory;
private final Server server;
private final int serverPort;
@@ -31,12 +31,12 @@ public class NitroWebsocketProxy {
HStateSetter stateSetter,
HConnection connection,
NitroProxyProvider proxyProvider,
- NitroCertificateSniffingManager certificateManager) {
+ NitroCertificateFactory certificateFactory) {
this.proxySetter = proxySetter;
this.stateSetter = stateSetter;
this.connection = connection;
this.proxyProvider = proxyProvider;
- this.certificateManager = certificateManager;
+ this.certificateFactory = certificateFactory;
this.server = new Server();
this.serverPort = 0;
}
@@ -44,7 +44,7 @@ public class NitroWebsocketProxy {
public boolean start() {
try {
// Configure SSL.
- final NitroSslContextFactory sslContextFactory = new NitroSslContextFactory(this.certificateManager);
+ final NitroSslContextFactory sslContextFactory = new NitroSslContextFactory(this.certificateFactory);
final ServerConnector sslConnector = new ServerConnector(server, sslContextFactory);
sslConnector.setPort(this.serverPort);
diff --git a/pom.xml b/pom.xml
index 52d38ed..2b23a73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
- 1.5.4-beta-9
+ 1.5.4-beta-10
-SNAPSHOT
1.5.3