mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-27 02:40:51 +01:00
Merge pull request #108 from UnfamiliarLegacy/fix/nitro-ssl
Fix nitro ssl certificate caching issue
This commit is contained in:
commit
44b54c93f3
@ -11,6 +11,9 @@ import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback;
|
|||||||
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
|
import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener {
|
public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener {
|
||||||
|
|
||||||
@ -19,8 +22,10 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
private final HConnection connection;
|
private final HConnection connection;
|
||||||
private final NitroHttpProxy nitroHttpProxy;
|
private final NitroHttpProxy nitroHttpProxy;
|
||||||
private final NitroWebsocketProxy nitroWebsocketProxy;
|
private final NitroWebsocketProxy nitroWebsocketProxy;
|
||||||
|
private final AtomicBoolean abortLock;
|
||||||
|
|
||||||
private String originalWebsocketUrl;
|
private String originalWebsocketUrl;
|
||||||
|
private String originalOriginUrl;
|
||||||
|
|
||||||
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) {
|
||||||
this.proxySetter = proxySetter;
|
this.proxySetter = proxySetter;
|
||||||
@ -28,12 +33,17 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.nitroHttpProxy = new NitroHttpProxy(this);
|
this.nitroHttpProxy = new NitroHttpProxy(this);
|
||||||
this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this);
|
this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this);
|
||||||
|
this.abortLock = new AtomicBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOriginalWebsocketUrl() {
|
public String getOriginalWebsocketUrl() {
|
||||||
return originalWebsocketUrl;
|
return originalWebsocketUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getOriginalOriginUrl() {
|
||||||
|
return originalOriginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
connection.getStateObservable().addListener(this);
|
connection.getStateObservable().addListener(this);
|
||||||
@ -55,6 +65,14 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abort() {
|
public void abort() {
|
||||||
|
if (abortLock.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortLock.compareAndSet(true, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stateSetter.setState(HState.ABORTING);
|
stateSetter.setState(HState.ABORTING);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@ -79,6 +97,8 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
@Override
|
@Override
|
||||||
public String replaceWebsocketServer(String configUrl, String websocketUrl) {
|
public String replaceWebsocketServer(String configUrl, String websocketUrl) {
|
||||||
originalWebsocketUrl = websocketUrl;
|
originalWebsocketUrl = websocketUrl;
|
||||||
|
originalOriginUrl = extractOriginUrl(configUrl);
|
||||||
|
|
||||||
return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT);
|
return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,5 +109,21 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa
|
|||||||
// We are not stopping the http proxy because some requests might still require it to be running.
|
// We are not stopping the http proxy because some requests might still require it to be running.
|
||||||
nitroHttpProxy.pause();
|
nitroHttpProxy.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Catch setState ABORTING inside NitroWebsocketClient.
|
||||||
|
if (newState == HState.ABORTING) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractOriginUrl(String url) {
|
||||||
|
try {
|
||||||
|
final URI uri = new URI(url);
|
||||||
|
return String.format("%s://%s/", uri.getScheme(), uri.getHost());
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
public NitroCertificateSniffingManager(Authority authority) throws RootCertificateException {
|
||||||
|
try {
|
||||||
|
sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true, null);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RootCertificateException("Errors during assembling root CA.", 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());
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ import javafx.scene.control.ButtonType;
|
|||||||
import org.littleshoot.proxy.HttpProxyServer;
|
import org.littleshoot.proxy.HttpProxyServer;
|
||||||
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
|
||||||
import org.littleshoot.proxy.mitm.Authority;
|
import org.littleshoot.proxy.mitm.Authority;
|
||||||
import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager;
|
|
||||||
import org.littleshoot.proxy.mitm.RootCertificateException;
|
import org.littleshoot.proxy.mitm.RootCertificateException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -55,7 +54,7 @@ public class NitroHttpProxy {
|
|||||||
ButtonType.YES, ButtonType.NO
|
ButtonType.YES, ButtonType.NO
|
||||||
);
|
);
|
||||||
|
|
||||||
shouldInstall.set(!(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent()));
|
shouldInstall.set(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent());
|
||||||
waitForDialog.release();
|
waitForDialog.release();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,7 +94,7 @@ public class NitroHttpProxy {
|
|||||||
try {
|
try {
|
||||||
proxyServer = DefaultHttpProxyServer.bootstrap()
|
proxyServer = DefaultHttpProxyServer.bootstrap()
|
||||||
.withPort(NitroConstants.HTTP_PORT)
|
.withPort(NitroConstants.HTTP_PORT)
|
||||||
.withManInTheMiddle(new CertificateSniffingMitmManager(authority))
|
.withManInTheMiddle(new NitroCertificateSniffingManager(authority))
|
||||||
.withFiltersSource(new NitroHttpProxyFilterSource(serverCallback))
|
.withFiltersSource(new NitroHttpProxyFilterSource(serverCallback))
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public class NitroWindows implements NitroOsFunctions {
|
|||||||
/**
|
/**
|
||||||
* Semicolon separated hosts to ignore for proxying.
|
* Semicolon separated hosts to ignore for proxying.
|
||||||
*/
|
*/
|
||||||
private static final String PROXY_IGNORE = "discord.com;github.com;";
|
private static final String PROXY_IGNORE = "discord.com;discordapp.com;github.com;";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the certificate is trusted by the local machine.
|
* Checks if the certificate is trusted by the local machine.
|
||||||
|
@ -3,6 +3,7 @@ package gearth.protocol.connection.proxy.nitro.websocket;
|
|||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.HPacket;
|
||||||
import gearth.protocol.packethandler.PacketHandler;
|
import gearth.protocol.packethandler.PacketHandler;
|
||||||
|
import gearth.protocol.packethandler.PayloadBuffer;
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
import gearth.services.extension_handler.OnHMessageHandled;
|
import gearth.services.extension_handler.OnHMessageHandled;
|
||||||
|
|
||||||
@ -14,11 +15,15 @@ public class NitroPacketHandler extends PacketHandler {
|
|||||||
|
|
||||||
private final HMessage.Direction direction;
|
private final HMessage.Direction direction;
|
||||||
private final NitroSession session;
|
private final NitroSession session;
|
||||||
|
private final PayloadBuffer payloadBuffer;
|
||||||
|
private final Object payloadLock;
|
||||||
|
|
||||||
protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) {
|
protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) {
|
||||||
super(extensionHandler, trafficObservables);
|
super(extensionHandler, trafficObservables);
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.payloadBuffer = new PayloadBuffer();
|
||||||
|
this.payloadLock = new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,20 +45,26 @@ public class NitroPacketHandler extends PacketHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void act(byte[] buffer) throws IOException {
|
public void act(byte[] buffer) throws IOException {
|
||||||
HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex);
|
payloadBuffer.push(buffer);
|
||||||
|
|
||||||
OnHMessageHandled afterExtensionIntercept = hMessage1 -> {
|
synchronized (payloadLock) {
|
||||||
notifyListeners(2, hMessage1);
|
for (HPacket packet : payloadBuffer.receive()) {
|
||||||
|
HMessage hMessage = new HMessage(packet, direction, currentIndex);
|
||||||
|
|
||||||
if (!hMessage1.isBlocked()) {
|
OnHMessageHandled afterExtensionIntercept = hMessage1 -> {
|
||||||
sendToStream(hMessage1.getPacket().toBytes());
|
notifyListeners(2, hMessage1);
|
||||||
|
|
||||||
|
if (!hMessage1.isBlocked()) {
|
||||||
|
sendToStream(hMessage1.getPacket().toBytes());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
notifyListeners(0, hMessage);
|
||||||
|
notifyListeners(1, hMessage);
|
||||||
|
extensionHandler.handle(hMessage, afterExtensionIntercept);
|
||||||
|
|
||||||
|
currentIndex++;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
notifyListeners(0, hMessage);
|
|
||||||
notifyListeners(1, hMessage);
|
|
||||||
extensionHandler.handle(hMessage, afterExtensionIntercept);
|
|
||||||
|
|
||||||
currentIndex++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ public class NitroWebsocketClient implements NitroSession {
|
|||||||
activeSession = session;
|
activeSession = session;
|
||||||
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||||
|
|
||||||
server.connect(proxyProvider.getOriginalWebsocketUrl());
|
server.connect(proxyProvider.getOriginalWebsocketUrl(), proxyProvider.getOriginalOriginUrl());
|
||||||
|
|
||||||
final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, "");
|
final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, "");
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ public class NitroWebsocketClient implements NitroSession {
|
|||||||
|
|
||||||
// Reset program state.
|
// Reset program state.
|
||||||
proxySetter.setProxy(null);
|
proxySetter.setProxy(null);
|
||||||
stateSetter.setState(HState.NOT_CONNECTED);
|
stateSetter.setState(HState.ABORTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,11 @@ import gearth.protocol.packethandler.PacketHandler;
|
|||||||
import javax.websocket.*;
|
import javax.websocket.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@ClientEndpoint
|
public class NitroWebsocketServer extends Endpoint implements NitroSession {
|
||||||
public class NitroWebsocketServer implements NitroSession {
|
|
||||||
|
|
||||||
private final PacketHandler packetHandler;
|
private final PacketHandler packetHandler;
|
||||||
private final NitroWebsocketClient client;
|
private final NitroWebsocketClient client;
|
||||||
@ -21,32 +23,50 @@ public class NitroWebsocketServer implements NitroSession {
|
|||||||
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
|
this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String websocketUrl) throws IOException {
|
public void connect(String websocketUrl, String originUrl) throws IOException {
|
||||||
try {
|
try {
|
||||||
ContainerProvider.getWebSocketContainer().connectToServer(this, URI.create(websocketUrl));
|
ClientEndpointConfig.Builder builder = ClientEndpointConfig.Builder.create();
|
||||||
|
|
||||||
|
if (originUrl != null) {
|
||||||
|
builder.configurator(new ClientEndpointConfig.Configurator() {
|
||||||
|
@Override
|
||||||
|
public void beforeRequest(Map<String, List<String>> headers) {
|
||||||
|
headers.put("Origin", Collections.singletonList(originUrl));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientEndpointConfig config = builder.build();
|
||||||
|
|
||||||
|
ContainerProvider.getWebSocketContainer().connectToServer(this, config, URI.create(websocketUrl));
|
||||||
} catch (DeploymentException e) {
|
} catch (DeploymentException e) {
|
||||||
throw new IOException("Failed to deploy websocket client", e);
|
throw new IOException("Failed to deploy websocket client", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnOpen
|
@Override
|
||||||
public void onOpen(Session Session) {
|
public void onOpen(Session session, EndpointConfig config) {
|
||||||
this.activeSession = Session;
|
this.activeSession = session;
|
||||||
this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE);
|
||||||
|
this.activeSession.addMessageHandler(new MessageHandler.Whole<byte[]>() {
|
||||||
|
@Override
|
||||||
|
public void onMessage(byte[] message) {
|
||||||
|
try {
|
||||||
|
packetHandler.act(message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnMessage
|
@Override
|
||||||
public void onMessage(byte[] b, Session session) throws IOException {
|
public void onClose(Session session, CloseReason closeReason) {
|
||||||
packetHandler.act(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClose
|
|
||||||
public void onClose(Session userSession, CloseReason reason) {
|
|
||||||
// Hotel closed connection.
|
// Hotel closed connection.
|
||||||
client.shutdownProxy();
|
client.shutdownProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnError
|
@Override
|
||||||
public void onError(Session session, Throwable throwable) {
|
public void onError(Session session, Throwable throwable) {
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user