From 09105e956b1ee7503c9be1141f6935f2036631b2 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 19:58:34 +0100 Subject: [PATCH] Prompt root certificate installation --- .../main/java/gearth/misc/RuntimeUtil.java | 31 ++++++++++++ .../proxy/nitro/http/NitroHttpProxy.java | 47 +++++++++++++++++++ .../proxy/nitro/os/NitroOsFunctions.java | 2 + .../proxy/nitro/os/windows/NitroWindows.java | 45 +++++++++++++++++- .../nitro/os/windows/NitroWindowsShell32.java | 17 +++++++ 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/misc/RuntimeUtil.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java diff --git a/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java new file mode 100644 index 0000000..0e17736 --- /dev/null +++ b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java @@ -0,0 +1,31 @@ +package gearth.misc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public final class RuntimeUtil { + + public static String getCommandOutput(String[] command) throws IOException { + try { + final Runtime rt = Runtime.getRuntime(); + final Process proc = rt.exec(command); + + final BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); + final StringBuilder result = new StringBuilder(); + + String line; + + while ((line = stdInput.readLine()) != null) { + result.append(line); + result.append("\n"); + } + + return result.toString(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } + } + +} 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 84885ee..475d912 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 @@ -1,16 +1,26 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.misc.ConfirmationDialog; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; +import javafx.application.Platform; +import javafx.scene.control.Alert; +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; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + public class NitroHttpProxy { + private static final String ADMIN_WARNING_KEY = "admin_warning_dialog"; + private final Authority authority; private final NitroOsFunctions osFunctions; private final NitroHttpProxyServerCallback serverCallback; @@ -24,6 +34,43 @@ public class NitroHttpProxy { } private boolean initializeCertificate() { + final File certificate = this.authority.aliasFile(".pem"); + + // All good if certificate is already trusted. + if (this.osFunctions.isRootCertificateTrusted(certificate)) { + return true; + } + + // Let the user know about admin permissions. + final Semaphore waitForDialog = new Semaphore(0); + final AtomicBoolean shouldInstall = new AtomicBoolean(); + + Platform.runLater(() -> { + Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY, + "Root certificate installation", null, + "G-Earth detected that you do not have the root certificate authority installed. " + + "This is required for Nitro to work, do you want to continue? " + + "G-Earth will ask you for Administrator permission if you do so.", "Remember my choice", + ButtonType.YES, ButtonType.NO + ); + + shouldInstall.set(!(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent())); + waitForDialog.release(); + }); + + // Wait for dialog choice. + try { + waitForDialog.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + + // User opted out. + if (!shouldInstall.get()) { + return false; + } + return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem")); } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java index d77a4c6..0bfaf29 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java @@ -4,6 +4,8 @@ import java.io.File; public interface NitroOsFunctions { + boolean isRootCertificateTrusted(File certificate); + boolean installRootCertificate(File certificate); boolean registerSystemProxy(String host, int port); 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 27c7b12..fc5abf5 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 @@ -1,15 +1,56 @@ 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.ptr.IntByReference; +import gearth.misc.RuntimeUtil; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import java.io.File; +import java.io.IOException; public class NitroWindows implements NitroOsFunctions { + /** + * Checks if the certificate is trusted by the local machine. + * @param certificate Absolute path to the certificate. + * @return true if trusted + */ + @Override + public boolean isRootCertificateTrusted(File certificate) { + try { + final String output = RuntimeUtil.getCommandOutput(new String[] {"cmd", "/c", " certutil.exe -f -verify " + certificate.getAbsolutePath()}); + + return !output.contains("CERT_TRUST_IS_UNTRUSTED_ROOT") && + output.contains("dwInfoStatus=10c dwErrorStatus=0"); + } catch (IOException e) { + e.printStackTrace(); + } + + return false; + } + @Override public boolean installRootCertificate(File certificate) { - // TODO: Prompt registration - System.out.println(certificate.toString()); + final String certificatePath = certificate.getAbsolutePath(); + + // Prompt UAC elevation. + WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(null, "runas", "cmd.exe", "/S /C \"certutil -addstore root " + certificatePath + "\"", null, 1); + + // Wait for exit. + Kernel32.INSTANCE.WaitForSingleObject(result, WinBase.INFINITE); + + // Exit code for certutil. + final IntByReference statusRef = new IntByReference(-1); + Kernel32.INSTANCE.GetExitCodeProcess(result, statusRef); + + // Check if process exited without errors + if (statusRef.getValue() != -1) { + System.out.printf("Certutil command exited with exit code %s%n", statusRef.getValue()); + return false; + } + return true; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java new file mode 100644 index 0000000..69087aa --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.os.windows; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.ShellAPI; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.win32.StdCallLibrary; + +public interface NitroWindowsShell32 extends ShellAPI, StdCallLibrary { + NitroWindowsShell32 INSTANCE = Native.loadLibrary("shell32", NitroWindowsShell32.class); + + WinDef.HINSTANCE ShellExecuteA(WinDef.HWND hwnd, + String lpOperation, + String lpFile, + String lpParameters, + String lpDirectory, + int nShowCmd); +}