Prompt root certificate installation

This commit is contained in:
UnfamiliarLegacy 2021-11-26 19:58:34 +01:00
parent 322cb05ceb
commit 09105e956b
5 changed files with 140 additions and 2 deletions

View File

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

View File

@ -1,16 +1,26 @@
package gearth.protocol.connection.proxy.nitro.http; package gearth.protocol.connection.proxy.nitro.http;
import gearth.misc.ConfirmationDialog;
import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroConstants;
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; 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.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.CertificateSniffingMitmManager;
import org.littleshoot.proxy.mitm.RootCertificateException; import org.littleshoot.proxy.mitm.RootCertificateException;
import java.io.File;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
public class NitroHttpProxy { public class NitroHttpProxy {
private static final String ADMIN_WARNING_KEY = "admin_warning_dialog";
private final Authority authority; private final Authority authority;
private final NitroOsFunctions osFunctions; private final NitroOsFunctions osFunctions;
private final NitroHttpProxyServerCallback serverCallback; private final NitroHttpProxyServerCallback serverCallback;
@ -24,6 +34,43 @@ public class NitroHttpProxy {
} }
private boolean initializeCertificate() { 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")); return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem"));
} }

View File

@ -4,6 +4,8 @@ import java.io.File;
public interface NitroOsFunctions { public interface NitroOsFunctions {
boolean isRootCertificateTrusted(File certificate);
boolean installRootCertificate(File certificate); boolean installRootCertificate(File certificate);
boolean registerSystemProxy(String host, int port); boolean registerSystemProxy(String host, int port);

View File

@ -1,15 +1,56 @@
package gearth.protocol.connection.proxy.nitro.os.windows; 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 gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions;
import java.io.File; import java.io.File;
import java.io.IOException;
public class NitroWindows implements NitroOsFunctions { 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 @Override
public boolean installRootCertificate(File certificate) { public boolean installRootCertificate(File certificate) {
// TODO: Prompt registration final String certificatePath = certificate.getAbsolutePath();
System.out.println(certificate.toString());
// 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; return true;
} }

View File

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