Merge pull request #108 from UnfamiliarLegacy/fix/nitro-ssl

Fix nitro ssl certificate caching issue
This commit is contained in:
sirjonasxx 2021-12-05 07:01:31 +01:00 committed by GitHub
commit 44b54c93f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 33 deletions

View File

@ -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;
} }
} }

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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.

View File

@ -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++;
} }
} }

View File

@ -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);
} }
} }
} }

View File

@ -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();