From f381de6faf40ba3398b9a158e9d0ce7296854f01 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 18:47:05 +0100 Subject: [PATCH 1/6] Add certificate sniffing manager code --- .../http/NitroCertificateSniffingManager.java | 94 +++++++++++++++++++ .../proxy/nitro/http/NitroHttpProxy.java | 2 +- .../proxy/nitro/os/windows/NitroWindows.java | 2 +- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java 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 new file mode 100644 index 0000000..d6d0c60 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroCertificateSniffingManager.java @@ -0,0 +1,94 @@ +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; + +/** + * {@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 BouncyCastleSslEngineSource sslEngineSource; + + public NitroCertificateSniffingManager(Authority authority) throws RootCertificateException { + try { + sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true); + } 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.printf("[NitroCertificateSniffingManager] Subject Alternative Names: %s%n", san); + } + + 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 231abb7..7219b8f 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 @@ -95,7 +95,7 @@ public class NitroHttpProxy { try { proxyServer = DefaultHttpProxyServer.bootstrap() .withPort(NitroConstants.HTTP_PORT) - .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) + .withManInTheMiddle(new NitroCertificateSniffingManager(authority)) .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); 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 049a6be..c5f4655 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 @@ -15,7 +15,7 @@ public class NitroWindows implements NitroOsFunctions { /** * 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. From 2de16e426412fe9a1772d08604b491f827a569fd Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 19:14:34 +0100 Subject: [PATCH 2/6] Disable BouncyCastleSslEngineSource cache --- .../nitro/http/NitroCertificateSniffingManager.java | 11 ++++++++--- .../connection/proxy/nitro/http/NitroHttpProxy.java | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) 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 index d6d0c60..35f4c2d 100644 --- 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 @@ -9,6 +9,7 @@ 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 @@ -18,11 +19,11 @@ public class NitroCertificateSniffingManager implements MitmManager { private static final boolean DEBUG = false; - private BouncyCastleSslEngineSource sslEngineSource; + private final BouncyCastleSslEngineSource sslEngineSource; public NitroCertificateSniffingManager(Authority authority) throws RootCertificateException { try { - sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true); + sslEngineSource = new BouncyCastleSslEngineSource(authority, true, true, null); } catch (final Exception e) { throw new RootCertificateException("Errors during assembling root CA.", e); } @@ -54,7 +55,11 @@ public class NitroCertificateSniffingManager implements MitmManager { san.addAll(upstreamCert.getSubjectAlternativeNames()); if (DEBUG) { - System.out.printf("[NitroCertificateSniffingManager] Subject Alternative Names: %s%n", san); + 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); 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 7219b8f..544b2ba 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 @@ -10,7 +10,6 @@ import javafx.scene.control.ButtonType; import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.mitm.Authority; -import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; import org.littleshoot.proxy.mitm.RootCertificateException; import java.io.File; From ad9728af288554a713afd77db701a689308113f5 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:43:48 +0100 Subject: [PATCH 3/6] Fix confirmation dialog yes/no --- .../protocol/connection/proxy/nitro/http/NitroHttpProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 544b2ba..cc897a6 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 @@ -54,7 +54,7 @@ public class NitroHttpProxy { 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(); }); From a525d6c8673e0c7a9e47fd75054398dcfd7771f4 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 20:51:11 +0100 Subject: [PATCH 4/6] Properly combine packets --- .../nitro/websocket/NitroPacketHandler.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index d6ed58a..70ed0cb 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -3,6 +3,7 @@ package gearth.protocol.connection.proxy.nitro.websocket; import gearth.protocol.HMessage; import gearth.protocol.HPacket; import gearth.protocol.packethandler.PacketHandler; +import gearth.protocol.packethandler.PayloadBuffer; import gearth.services.extension_handler.ExtensionHandler; import gearth.services.extension_handler.OnHMessageHandled; @@ -14,11 +15,15 @@ public class NitroPacketHandler extends PacketHandler { private final HMessage.Direction direction; private final NitroSession session; + private final PayloadBuffer payloadBuffer; + private final Object payloadLock; protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) { super(extensionHandler, trafficObservables); this.direction = direction; this.session = session; + this.payloadBuffer = new PayloadBuffer(); + this.payloadLock = new Object(); } @Override @@ -40,20 +45,26 @@ public class NitroPacketHandler extends PacketHandler { @Override public void act(byte[] buffer) throws IOException { - HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex); + payloadBuffer.push(buffer); - OnHMessageHandled afterExtensionIntercept = hMessage1 -> { - notifyListeners(2, hMessage1); + synchronized (payloadLock) { + for (HPacket packet : payloadBuffer.receive()) { + HMessage hMessage = new HMessage(packet, direction, currentIndex); - if (!hMessage1.isBlocked()) { - sendToStream(hMessage1.getPacket().toBytes()); + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + 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++; + } } } From f49906897c646bd2abdf9e5c1d2eb5714b2fee18 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:09:50 +0100 Subject: [PATCH 5/6] Extract origin url from config path --- .../proxy/nitro/NitroProxyProvider.java | 20 ++++++++ .../nitro/websocket/NitroWebsocketClient.java | 2 +- .../nitro/websocket/NitroWebsocketServer.java | 50 +++++++++++++------ 3 files changed, 56 insertions(+), 16 deletions(-) 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 3cb47a1..b8c0810 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 @@ -11,6 +11,8 @@ import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback; import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { @@ -21,6 +23,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa private final NitroWebsocketProxy nitroWebsocketProxy; private String originalWebsocketUrl; + private String originalOriginUrl; public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) { this.proxySetter = proxySetter; @@ -34,6 +37,10 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa return originalWebsocketUrl; } + public String getOriginalOriginUrl() { + return originalOriginUrl; + } + @Override public void start() throws IOException { connection.getStateObservable().addListener(this); @@ -79,6 +86,8 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public String replaceWebsocketServer(String configUrl, String websocketUrl) { originalWebsocketUrl = websocketUrl; + originalOriginUrl = extractOriginUrl(configUrl); + return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT); } @@ -90,4 +99,15 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa nitroHttpProxy.pause(); } } + + 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; + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index aabb946..59bf027 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -39,7 +39,7 @@ public class NitroWebsocketClient implements NitroSession { activeSession = session; 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, ""); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java index 04b238c..b54b96c 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -8,9 +8,11 @@ import gearth.protocol.packethandler.PacketHandler; import javax.websocket.*; import java.io.IOException; import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; -@ClientEndpoint -public class NitroWebsocketServer implements NitroSession { +public class NitroWebsocketServer extends Endpoint implements NitroSession { private final PacketHandler packetHandler; 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()); } - public void connect(String websocketUrl) throws IOException { + public void connect(String websocketUrl, String originUrl) throws IOException { 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> headers) { + headers.put("Origin", Collections.singletonList(originUrl)); + } + }); + } + + ClientEndpointConfig config = builder.build(); + + ContainerProvider.getWebSocketContainer().connectToServer(this, config, URI.create(websocketUrl)); } catch (DeploymentException e) { throw new IOException("Failed to deploy websocket client", e); } } - @OnOpen - public void onOpen(Session Session) { - this.activeSession = Session; + @Override + public void onOpen(Session session, EndpointConfig config) { + this.activeSession = session; this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + this.activeSession.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(byte[] message) { + try { + packetHandler.act(message); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); } - @OnMessage - public void onMessage(byte[] b, Session session) throws IOException { - packetHandler.act(b); - } - - @OnClose - public void onClose(Session userSession, CloseReason reason) { + @Override + public void onClose(Session session, CloseReason closeReason) { // Hotel closed connection. client.shutdownProxy(); } - @OnError + @Override public void onError(Session session, Throwable throwable) { throwable.printStackTrace(); From e0cb283ab756df5a95bbb6647656e9072835054e Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Wed, 1 Dec 2021 23:38:33 +0100 Subject: [PATCH 6/6] Properly abort when client closes connection --- .../proxy/nitro/NitroProxyProvider.java | 16 ++++++++++++++++ .../nitro/websocket/NitroWebsocketClient.java | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) 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 b8c0810..5b90922 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 @@ -13,6 +13,7 @@ import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; 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 { @@ -21,6 +22,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa private final HConnection connection; private final NitroHttpProxy nitroHttpProxy; private final NitroWebsocketProxy nitroWebsocketProxy; + private final AtomicBoolean abortLock; private String originalWebsocketUrl; private String originalOriginUrl; @@ -31,6 +33,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa this.connection = connection; this.nitroHttpProxy = new NitroHttpProxy(this); this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this); + this.abortLock = new AtomicBoolean(); } public String getOriginalWebsocketUrl() { @@ -62,6 +65,14 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public void abort() { + if (abortLock.get()) { + return; + } + + if (abortLock.compareAndSet(true, true)) { + return; + } + stateSetter.setState(HState.ABORTING); new Thread(() -> { @@ -98,6 +109,11 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa // We are not stopping the http proxy because some requests might still require it to be running. nitroHttpProxy.pause(); } + + // Catch setState ABORTING inside NitroWebsocketClient. + if (newState == HState.ABORTING) { + abort(); + } } private static String extractOriginUrl(String url) { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index 59bf027..ca7e88b 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -112,7 +112,7 @@ public class NitroWebsocketClient implements NitroSession { // Reset program state. proxySetter.setProxy(null); - stateSetter.setState(HState.NOT_CONNECTED); + stateSetter.setState(HState.ABORTING); } } }