mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-23 08:50:52 +01:00
Nitro http proxy rewrite to proxyee progress
This commit is contained in:
parent
97eb397d9e
commit
fe97142bfe
4
.gitignore
vendored
4
.gitignore
vendored
@ -25,4 +25,6 @@ Extensions/BlockReplacePackets/.classpath
|
|||||||
|
|
||||||
# Certificates
|
# Certificates
|
||||||
*.p12
|
*.p12
|
||||||
*.pem
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.key
|
@ -313,20 +313,38 @@
|
|||||||
<version>1.5.0</version>
|
<version>1.5.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.ganskef</groupId>
|
<groupId>com.github.monkeywie</groupId>
|
||||||
<artifactId>littleproxy-mitm</artifactId>
|
<artifactId>proxyee</artifactId>
|
||||||
<version>1.1.0</version>
|
<version>1.7.6</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>slf4j-log4j12</artifactId>
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
|
<version>1.78.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
|
<version>1.78.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bctls-jdk18on -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bctls-jdk18on</artifactId>
|
||||||
|
<version>1.78.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>G-Earth</groupId>
|
<groupId>G-Earth</groupId>
|
||||||
<artifactId>G-Wasm-Minimal</artifactId>
|
<artifactId>G-Wasm-Minimal</artifactId>
|
||||||
|
@ -122,8 +122,8 @@ public class GEarth extends Application {
|
|||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
primaryStage.setOnCloseRequest(event -> closeGEarth());
|
primaryStage.setOnCloseRequest(event -> closeGEarth());
|
||||||
|
|
||||||
AdminValidator.validate();
|
//AdminValidator.validate();
|
||||||
UpdateChecker.checkForUpdates();
|
//UpdateChecker.checkForUpdates();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,4 +9,22 @@ public final class NitroConstants {
|
|||||||
public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768";
|
public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768";
|
||||||
public static final String WEBSOCKET_CLIENT_IDENTIFIER = "HTML5";
|
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"
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,7 @@ import gearth.protocol.connection.HProxySetter;
|
|||||||
import gearth.protocol.connection.HState;
|
import gearth.protocol.connection.HState;
|
||||||
import gearth.protocol.connection.HStateSetter;
|
import gearth.protocol.connection.HStateSetter;
|
||||||
import gearth.protocol.connection.proxy.ProxyProvider;
|
import gearth.protocol.connection.proxy.ProxyProvider;
|
||||||
import gearth.protocol.connection.proxy.nitro.http.NitroAuthority;
|
import gearth.protocol.connection.proxy.nitro.http.NitroCertificateFactory;
|
||||||
import gearth.protocol.connection.proxy.nitro.http.NitroCertificateSniffingManager;
|
|
||||||
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy;
|
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy;
|
||||||
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback;
|
import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback;
|
||||||
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
|
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
|
||||||
@ -33,8 +32,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
private String originalCookies;
|
private String originalCookies;
|
||||||
|
|
||||||
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
||||||
final NitroAuthority authority = new NitroAuthority();
|
final NitroCertificateFactory certificateManager = new NitroCertificateFactory();
|
||||||
final NitroCertificateSniffingManager certificateManager = new NitroCertificateSniffingManager(authority);
|
|
||||||
|
|
||||||
this.proxySetter = proxySetter;
|
this.proxySetter = proxySetter;
|
||||||
this.stateSetter = stateSetter;
|
this.stateSetter = stateSetter;
|
||||||
@ -103,7 +101,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
try {
|
try {
|
||||||
nitroHttpProxy.stop();
|
nitroHttpProxy.stop();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
logger.error("Failed to stop nitro http proxy", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Stopping nitro websocket proxy");
|
logger.info("Stopping nitro websocket proxy");
|
||||||
@ -111,7 +109,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
try {
|
try {
|
||||||
nitroWebsocketProxy.stop();
|
nitroWebsocketProxy.stop();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
logger.error("Failed to stop nitro websocket proxy", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
stateSetter.setState(HState.NOT_CONNECTED);
|
stateSetter.setState(HState.NOT_CONNECTED);
|
||||||
|
@ -1,24 +1,9 @@
|
|||||||
package gearth.protocol.connection.proxy.nitro.http;
|
package gearth.protocol.connection.proxy.nitro.http;
|
||||||
|
|
||||||
import org.littleshoot.proxy.mitm.Authority;
|
public class NitroAuthority {
|
||||||
|
|
||||||
import java.io.File;
|
public static final String CERT_ALIAS = "gearth-nitro-v2";
|
||||||
|
public static final String CERT_ORGANIZATION = "G-Earth Nitro";
|
||||||
public class NitroAuthority extends Authority {
|
public static final String CERT_DESCRIPTION = "G-Earth nitro support v2";
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +1,57 @@
|
|||||||
package gearth.protocol.connection.proxy.nitro.http;
|
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.misc.ConfirmationDialog;
|
||||||
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
import gearth.protocol.connection.proxy.nitro.NitroConstants;
|
||||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
||||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory;
|
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory;
|
||||||
import gearth.ui.titlebar.TitleBarController;
|
import gearth.ui.titlebar.TitleBarController;
|
||||||
import gearth.ui.translations.LanguageBundle;
|
import gearth.ui.translations.LanguageBundle;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import org.littleshoot.proxy.HttpProxyServer;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
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.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.Semaphore;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class NitroHttpProxy {
|
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 String ADMIN_WARNING_KEY = "admin_warning_dialog";
|
||||||
private static final AtomicBoolean SHUTDOWN_HOOK = new AtomicBoolean();
|
private static final AtomicBoolean SHUTDOWN_HOOK = new AtomicBoolean();
|
||||||
|
|
||||||
private final NitroOsFunctions osFunctions;
|
private final NitroOsFunctions osFunctions;
|
||||||
private final NitroHttpProxyServerCallback serverCallback;
|
private final NitroHttpProxyServerCallback serverCallback;
|
||||||
private final NitroCertificateSniffingManager certificateManager;
|
private final NitroCertificateFactory certificateFactory;
|
||||||
|
|
||||||
private HttpProxyServer proxyServer = null;
|
private HttpProxyServer proxyServer = null;
|
||||||
|
|
||||||
public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback, NitroCertificateSniffingManager certificateManager) {
|
public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback, NitroCertificateFactory certificateManager) {
|
||||||
this.serverCallback = serverCallback;
|
this.serverCallback = serverCallback;
|
||||||
this.certificateManager = certificateManager;
|
this.certificateFactory = certificateManager;
|
||||||
this.osFunctions = NitroOsFunctionsFactory.create();
|
this.osFunctions = NitroOsFunctionsFactory.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean initializeCertificate() {
|
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.
|
// All good if certificate is already trusted.
|
||||||
if (this.osFunctions.isRootCertificateTrusted(certificate)) {
|
if (this.osFunctions.isRootCertificateTrusted(certificate)) {
|
||||||
@ -48,28 +63,31 @@ public class NitroHttpProxy {
|
|||||||
final AtomicBoolean shouldInstall = new AtomicBoolean();
|
final AtomicBoolean shouldInstall = new AtomicBoolean();
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
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 {
|
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()
|
shouldInstall.set(TitleBarController.create(alert).showAlertAndWait()
|
||||||
.filter(t -> t == ButtonType.YES).isPresent());
|
.filter(t -> t == ButtonType.YES).isPresent());
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("Failed to show install dialog", e);
|
||||||
|
} finally {
|
||||||
|
waitForDialog.release();
|
||||||
}
|
}
|
||||||
waitForDialog.release();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for dialog choice.
|
// Wait for dialog choice.
|
||||||
try {
|
try {
|
||||||
waitForDialog.acquire();
|
waitForDialog.acquire();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
log.error("Interrupted while waiting for user input", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +96,7 @@ public class NitroHttpProxy {
|
|||||||
return false;
|
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() {
|
public boolean start() {
|
||||||
setupShutdownHook();
|
setupShutdownHook();
|
||||||
|
|
||||||
proxyServer = DefaultHttpProxyServer.bootstrap()
|
|
||||||
.withPort(NitroConstants.HTTP_PORT)
|
|
||||||
.withManInTheMiddle(this.certificateManager)
|
|
||||||
.withFiltersSource(new NitroHttpProxyFilterSource(serverCallback))
|
|
||||||
.withTransparent(true)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
if (!initializeCertificate()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!registerProxy()) {
|
if (!registerProxy()) {
|
||||||
proxyServer.stop();
|
proxyServer.close();
|
||||||
|
|
||||||
System.out.println("Failed to register certificate");
|
log.error("Failed to register system proxy");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +162,7 @@ public class NitroHttpProxy {
|
|||||||
|
|
||||||
public void pause() {
|
public void pause() {
|
||||||
if (!unregisterProxy()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyServer.stop();
|
proxyServer.close();
|
||||||
proxyServer = null;
|
proxyServer = 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<String> 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<String> result = new ArrayList<>();
|
|
||||||
final List<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,15 +6,15 @@ import javax.net.ssl.SSLEngine;
|
|||||||
|
|
||||||
public class NitroSslContextFactory extends SslContextFactory.Server {
|
public class NitroSslContextFactory extends SslContextFactory.Server {
|
||||||
|
|
||||||
private final NitroCertificateSniffingManager certificateManager;
|
private final NitroCertificateFactory certificateFactory;
|
||||||
|
|
||||||
public NitroSslContextFactory(NitroCertificateSniffingManager certificateManager) {
|
public NitroSslContextFactory(NitroCertificateFactory certificateFactory) {
|
||||||
this.certificateManager = certificateManager;
|
this.certificateFactory = certificateFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SSLEngine newSSLEngine(String host, int port) {
|
public SSLEngine newSSLEngine(String host, int port) {
|
||||||
System.out.printf("[NitroSslContextFactory] Creating SSLEngine for %s:%d%n", host, port);
|
System.out.printf("[NitroSslContextFactory] Creating SSLEngine for %s:%d%n", host, port);
|
||||||
return certificateManager.websocketSslEngine(host);
|
return certificateFactory.websocketSslEngine(host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ public class NitroMacOS implements NitroOsFunctions {
|
|||||||
/**
|
/**
|
||||||
* Semicolon separated hosts to ignore for proxying.
|
* 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.
|
* Checks if the certificate is trusted by the local machine.
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
package gearth.protocol.connection.proxy.nitro.os.windows;
|
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.platform.win32.WinDef;
|
||||||
import com.sun.jna.ptr.IntByReference;
|
|
||||||
import gearth.misc.RuntimeUtil;
|
import gearth.misc.RuntimeUtil;
|
||||||
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class NitroWindows implements NitroOsFunctions {
|
public class NitroWindows implements NitroOsFunctions {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NitroWindows.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Semicolon separated hosts to ignore for proxying.
|
* Semicolon separated hosts to ignore for proxying.
|
||||||
*/
|
*/
|
||||||
// habba.io;
|
|
||||||
private static final String PROXY_IGNORE = "discord.com;discordapp.com;github.com;challenges.cloudflare.com;";
|
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") &&
|
return !output.contains("CERT_TRUST_IS_UNTRUSTED_ROOT") &&
|
||||||
output.contains("dwInfoStatus=10c dwErrorStatus=0");
|
output.contains("dwInfoStatus=10c dwErrorStatus=0");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("Failed to check if root certificate is trusted", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -39,21 +39,27 @@ public class NitroWindows implements NitroOsFunctions {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean installRootCertificate(File certificate) {
|
public boolean installRootCertificate(File certificate) {
|
||||||
final String certificatePath = certificate.getAbsolutePath();
|
final String certificatePath = certificate.toPath().normalize().toAbsolutePath().toString();
|
||||||
|
|
||||||
// Prompt UAC elevation.
|
// Correct the command for certutil
|
||||||
WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(null, "runas", "cmd.exe", "/S /C 'certutil -addstore root \"" + certificatePath + "\"'", null, 1);
|
final String installCommand = "/c certutil -addstore root \"" + certificatePath + "\"";
|
||||||
|
|
||||||
// Wait for exit.
|
log.debug("Installing root certificate with command: {}", installCommand);
|
||||||
Kernel32.INSTANCE.WaitForSingleObject(result, WinBase.INFINITE);
|
|
||||||
|
|
||||||
// Exit code for certutil.
|
// Prompt UAC elevation using ShellExecuteA with "runas"
|
||||||
final IntByReference statusRef = new IntByReference(-1);
|
WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(
|
||||||
Kernel32.INSTANCE.GetExitCodeProcess(result, statusRef);
|
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
|
final int resultValue = result.toNative().hashCode();
|
||||||
if (statusRef.getValue() != -1) {
|
|
||||||
System.out.printf("Certutil command exited with exit code %s%n", statusRef.getValue());
|
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;
|
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");
|
Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f");
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("Failed to register system proxy", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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");
|
Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 0 /f");
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error("Failed to unregister system proxy", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -4,7 +4,7 @@ import gearth.protocol.HConnection;
|
|||||||
import gearth.protocol.connection.HProxySetter;
|
import gearth.protocol.connection.HProxySetter;
|
||||||
import gearth.protocol.connection.HStateSetter;
|
import gearth.protocol.connection.HStateSetter;
|
||||||
import gearth.protocol.connection.proxy.nitro.NitroProxyProvider;
|
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 gearth.protocol.connection.proxy.nitro.http.NitroSslContextFactory;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
@ -22,7 +22,7 @@ public class NitroWebsocketProxy {
|
|||||||
private final HStateSetter stateSetter;
|
private final HStateSetter stateSetter;
|
||||||
private final HConnection connection;
|
private final HConnection connection;
|
||||||
private final NitroProxyProvider proxyProvider;
|
private final NitroProxyProvider proxyProvider;
|
||||||
private final NitroCertificateSniffingManager certificateManager;
|
private final NitroCertificateFactory certificateFactory;
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final int serverPort;
|
private final int serverPort;
|
||||||
@ -31,12 +31,12 @@ public class NitroWebsocketProxy {
|
|||||||
HStateSetter stateSetter,
|
HStateSetter stateSetter,
|
||||||
HConnection connection,
|
HConnection connection,
|
||||||
NitroProxyProvider proxyProvider,
|
NitroProxyProvider proxyProvider,
|
||||||
NitroCertificateSniffingManager certificateManager) {
|
NitroCertificateFactory certificateFactory) {
|
||||||
this.proxySetter = proxySetter;
|
this.proxySetter = proxySetter;
|
||||||
this.stateSetter = stateSetter;
|
this.stateSetter = stateSetter;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.proxyProvider = proxyProvider;
|
this.proxyProvider = proxyProvider;
|
||||||
this.certificateManager = certificateManager;
|
this.certificateFactory = certificateFactory;
|
||||||
this.server = new Server();
|
this.server = new Server();
|
||||||
this.serverPort = 0;
|
this.serverPort = 0;
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ public class NitroWebsocketProxy {
|
|||||||
public boolean start() {
|
public boolean start() {
|
||||||
try {
|
try {
|
||||||
// Configure SSL.
|
// Configure SSL.
|
||||||
final NitroSslContextFactory sslContextFactory = new NitroSslContextFactory(this.certificateManager);
|
final NitroSslContextFactory sslContextFactory = new NitroSslContextFactory(this.certificateFactory);
|
||||||
final ServerConnector sslConnector = new ServerConnector(server, sslContextFactory);
|
final ServerConnector sslConnector = new ServerConnector(server, sslContextFactory);
|
||||||
|
|
||||||
sslConnector.setPort(this.serverPort);
|
sslConnector.setPort(this.serverPort);
|
||||||
|
2
pom.xml
2
pom.xml
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- Version of the application. -->
|
<!-- Version of the application. -->
|
||||||
<revision>1.5.4-beta-9</revision>
|
<revision>1.5.4-beta-10</revision>
|
||||||
<changelist>-SNAPSHOT</changelist>
|
<changelist>-SNAPSHOT</changelist>
|
||||||
<!-- Version for https://github.com/sirjonasxx/G-ExtensionStore to keep compatibility with beta versions. -->
|
<!-- Version for https://github.com/sirjonasxx/G-ExtensionStore to keep compatibility with beta versions. -->
|
||||||
<storeVersion>1.5.3</storeVersion>
|
<storeVersion>1.5.3</storeVersion>
|
||||||
|
Loading…
Reference in New Issue
Block a user