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