From de7d7a9cca13b632feec39f37e3ea5f97212049c Mon Sep 17 00:00:00 2001 From: Sebastian Erives Date: Wed, 20 Nov 2024 19:13:24 -0600 Subject: [PATCH] Autoaccept super access coming from trusted source & bundle PaperVision --- Common/build.gradle | 7 ++++ .../plugin/security/PluginSigningTool.kt | 2 +- .../serivesmejia/eocvsim/config/Config.java | 2 + .../eocvsim/gui/dialog/Configuration.java | 18 +++++++-- .../eocvsim/plugin/loader/PluginLoader.kt | 6 ++- .../eocvsim/plugin/loader/PluginManager.kt | 8 +++- .../security/superaccess/SuperAccessDaemon.kt | 40 +++++++++++++------ .../superaccess/SuperAccessDaemonClient.kt | 31 +++++++------- EOCV-Sim/src/main/resources/repository.toml | 9 +++-- 9 files changed, 84 insertions(+), 39 deletions(-) diff --git a/Common/build.gradle b/Common/build.gradle index 0226af8..039e959 100644 --- a/Common/build.gradle +++ b/Common/build.gradle @@ -1,11 +1,18 @@ plugins { id 'kotlin' id 'signing' + id 'com.github.johnrengelman.shadow' id "com.vanniktech.maven.publish" version "0.30.0" } apply from: '../build.common.gradle' +shadowJar { + dependencies { + exclude "nu/pattern/*" + } +} + dependencies { api "org.openpnp:opencv:$opencv_version" diff --git a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt index 6b5189e..181b525 100644 --- a/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt +++ b/Common/src/main/java/io/github/deltacv/eocvsim/plugin/security/PluginSigningTool.kt @@ -92,7 +92,7 @@ class PluginSigningTool : Runnable { privateKeyFile.readBytes() } - val keyString = String(keyBytes) + val keyString = String(keyBytes, Charsets.UTF_8) val decodedKey = Base64.getDecoder().decode(AuthorityFetcher.parsePem(keyString)) val keySpec = PKCS8EncodedKeySpec(decodedKey) diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java index 0bc7f0d..cafad06 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/config/Config.java @@ -67,6 +67,8 @@ public class Config { @Deprecated public volatile List superAccessPluginHashes = new ArrayList<>(); + public volatile boolean autoAcceptSuperAccessOnTrusted = true; + public volatile HashMap flags = new HashMap<>(); public boolean hasFlag(String flagName) { diff --git a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java index 90b6f0f..eda46e8 100644 --- a/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java +++ b/EOCV-Sim/src/main/java/com/github/serivesmejia/eocvsim/gui/dialog/Configuration.java @@ -44,10 +44,11 @@ public class Configuration { private final EOCVSim eocvSim; public JPanel contents = new JPanel(new GridBagLayout()); public JComboBox themeComboBox = new JComboBox<>(); + public JCheckBox superAccessCheckBox = new JCheckBox("Auto Accept SuperAccess on Trusted Plugins"); public JButton acceptButton = new JButton("Accept"); - public JCheckBox pauseOnImageCheckBox = new JCheckBox(); + public JCheckBox pauseOnImageCheckBox = new JCheckBox("Pause with Image Sources"); public EnumComboBox pipelineTimeoutComboBox = null; public EnumComboBox pipelineFpsComboBox = null; @@ -81,7 +82,7 @@ private void initConfiguration() { UI TAB */ - JPanel uiPanel = new JPanel(new GridLayout(1, 1, 1, 8)); + JPanel uiPanel = new JPanel(new GridLayout(2, 1, 1, 8)); /* THEME SETTING */ JPanel themePanel = new JPanel(new FlowLayout()); @@ -98,6 +99,16 @@ private void initConfiguration() { themePanel.add(this.themeComboBox); uiPanel.add(themePanel); + /* AUTO ACCEPT SUPERACCESS ON TRUSTED PLUGINS */ + + JPanel superAccessPanel = new JPanel(new FlowLayout()); + + superAccessCheckBox.setSelected(config.autoAcceptSuperAccessOnTrusted); + + superAccessPanel.add(superAccessCheckBox); + + uiPanel.add(superAccessPanel); + tabbedPane.addTab("Interface", uiPanel); /* @@ -107,12 +118,10 @@ private void initConfiguration() { /* PAUSE WITH IMAGE SOURCES OPTION */ JPanel pauseOnImagePanel = new JPanel(new FlowLayout()); - JLabel pauseOnImageLabel = new JLabel("Pause with Image Sources"); pauseOnImageCheckBox.setSelected(config.pauseOnImages); pauseOnImagePanel.add(pauseOnImageCheckBox); - pauseOnImagePanel.add(pauseOnImageLabel); inputSourcesPanel.add(pauseOnImagePanel); @@ -215,6 +224,7 @@ private void applyChanges() { config.pipelineMaxFps = pipelineFpsComboBox.getSelectedEnum(); config.videoRecordingSize = videoRecordingSize.getCurrentSize(); config.videoRecordingFps = videoRecordingFpsComboBox.getSelectedEnum(); + config.autoAcceptSuperAccessOnTrusted = superAccessCheckBox.isSelected(); eocvSim.configManager.saveToFile(); //update config file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt index 0fc6672..3f43c49 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginLoader.kt @@ -218,7 +218,11 @@ class PluginLoader( } pluginClass = pluginClassLoader.loadClassStrict(pluginToml.getString("main")) - plugin = pluginClass.getConstructor().newInstance() as EOCVSimPlugin + plugin = try { + pluginClass.getConstructor().newInstance() as EOCVSimPlugin + } catch(e: Throwable) { + throw InvalidPluginException("Failed to instantiate plugin class ${pluginClass.name}. Make sure your plugin class has a public no-args constructor.") + } plugin.onLoad() diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt index d8c713e..56e98dc 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/loader/PluginManager.kt @@ -57,7 +57,11 @@ class PluginManager(val eocvSim: EOCVSim) { val logger by loggerForThis() - val superAccessDaemonClient = SuperAccessDaemonClient() + val superAccessDaemonClient by lazy { + SuperAccessDaemonClient( + autoacceptOnTrusted = eocvSim.config.autoAcceptSuperAccessOnTrusted + ) + } private val _loadedPluginHashes = mutableListOf() val loadedPluginHashes get() = _loadedPluginHashes.toList() @@ -270,6 +274,8 @@ class PluginManager(val eocvSim: EOCVSim) { haltCondition.await() } + appender.appendln(PluginOutput.SPECIAL_SILENT + "Super access for ${loader.pluginName} v${loader.pluginVersion} was ${if(access) "granted" else "denied"}") + return access } } \ No newline at end of file diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt index 1d8db01..dd706c7 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemon.kt @@ -41,7 +41,9 @@ import org.java_websocket.client.WebSocketClient import org.java_websocket.handshake.ServerHandshake import java.io.File import java.lang.Exception +import java.lang.Thread.sleep import java.net.URI +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors import java.util.zip.ZipFile import javax.swing.SwingUtilities @@ -73,29 +75,30 @@ object SuperAccessDaemon { val SUPERACCESS_FILE = PluginManager.PLUGIN_CACHING_FOLDER + File.separator + "superaccess.txt" - private val access = mutableMapOf() + private val access = ConcurrentHashMap() @JvmStatic fun main(args: Array) { - if(args.isEmpty()) { - logger.error("Port is required.") - return + if(args.size < 2) { + logger.error("Usage: ") + exitProcess(-1) } - if(args.size > 1) { + if(args.size > 2) { logger.warn("Ignoring extra arguments.") } System.setProperty("sun.java2d.d3d", "false") - WsClient(args[0].toIntOrNull() ?: throw IllegalArgumentException("Port is not a valid int")).connect() + WsClient(args[0].toIntOrNull() ?: throw IllegalArgumentException("Port is not a valid int"), args[1].toBoolean()).connect() } - class WsClient(port: Int) : WebSocketClient(URI("ws://localhost:$port")) { + class WsClient(port: Int, val autoacceptTrusted: Boolean) : WebSocketClient(URI("ws://localhost:$port")) { private val executor = Executors.newFixedThreadPool(4) override fun onOpen(p0: ServerHandshake?) { - logger.info("SuperAccessDaemon connection opened") + logger.info("SuperAccessDaemon connection opened.") + logger.info("Autoaccept on trusted: $autoacceptTrusted") } override fun onMessage(msg: String) { @@ -164,21 +167,32 @@ object SuperAccessDaemon { warning += "

$reason" } + // helper function to grant access, avoid code duplication + fun grant() { + SUPERACCESS_FILE.appendText(pluginFile.fileHash() + "\n") + accessGranted(message.id, message.pluginPath) + } + warning += if(validAuthority != null) { - "

This plugin has been digitally signed by ${validAuthority.name}, ensuring its integrity and authenticity.
${validAuthority.name} is a trusted authority in the EOCV-Sim ecosystem." + "

This plugin has been digitally signed by ${validAuthority.name}.
It is a trusted authority in the EOCV-Sim ecosystem." } else if(untrusted) { - "

This plugin claims to be made by trusted authority ${parser.pluginAuthor}, but it has not been digitally signed by them.

Beware of potential security risks.

" + "

This plugin claims to be made by ${parser.pluginAuthor}, but it has not been digitally signed by them.

Beware of potential security risks.

" } else { GENERIC_LAWYER_YEET } warning += "" + if(validAuthority != null && autoacceptTrusted) { + grant() + logger.info("Granted automatic SuperAccess to $name. The plugin has been signed by ${validAuthority.name}") + return + } + SwingUtilities.invokeLater { - SuperAccessRequest(name, warning, validAuthority == null || untrusted) { granted -> + SuperAccessRequest(name, warning, validAuthority == null) { granted -> if(granted) { - SUPERACCESS_FILE.appendText(pluginFile.fileHash() + "\n") - accessGranted(message.id, message.pluginPath) + grant() } else { accessDenied(message.id, message.pluginPath) } diff --git a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt index 357222a..70541aa 100644 --- a/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt +++ b/EOCV-Sim/src/main/java/io/github/deltacv/eocvsim/plugin/security/superaccess/SuperAccessDaemonClient.kt @@ -43,20 +43,11 @@ private data class AccessCache( val file: String, val hasAccess: Boolean, val timestamp: Long -) { - init { - // get calling class - val stackTrace = Thread.currentThread().stackTrace - val callingClass = stackTrace[2].className - - if(callingClass != SuperAccessDaemonClient::class.java.name) { - throw IllegalAccessException("AccessCache should only be created from SuperAccessDaemonClient") - } - } -} +) class SuperAccessDaemonClient( - val cacheTTLMillis: Long = 3_000 + val cacheTTLMillis: Long = 3_000, + autoacceptOnTrusted: Boolean ) { val logger by loggerForThis() @@ -68,7 +59,7 @@ class SuperAccessDaemonClient( private val accessCache = mutableMapOf() // create a new WebSocket server - private val server = WsServer(startLock, startCondition) + private val server = WsServer(startLock, startCondition, autoacceptOnTrusted) fun init() { server.start() @@ -89,10 +80,19 @@ class SuperAccessDaemonClient( server.broadcast(SuperAccessDaemon.gson.toJson(request)) server.addResponseReceiver(request.id) { response -> - if(response is SuperAccessDaemon.SuperAccessResponse.Success) { + var result = if(response is SuperAccessDaemon.SuperAccessResponse.Success) { onResponse(true) + true } else if(response is SuperAccessDaemon.SuperAccessResponse.Failure) { onResponse(false) + false + } else null + + if(result != null) { + synchronized(cacheLock) { + val newCache = AccessCache(request.pluginPath, result, System.currentTimeMillis()) + accessCache[request.pluginPath] = newCache + } } } } @@ -147,6 +147,7 @@ class SuperAccessDaemonClient( private class WsServer( val startLock: ReentrantLock, val startCondition: Condition, + val autoacceptOnTrusted: Boolean ) : WebSocketServer(InetSocketAddress("127.0.0.1", 0)) { // create an executor with 1 thread private val executor = Executors.newSingleThreadExecutor() @@ -220,7 +221,7 @@ class SuperAccessDaemonClient( SuperAccessDaemon::class.java, JavaProcess.SLF4JIOReceiver(logger), listOf("-Dlog4j.configurationFile=log4j2_nofile.xml"), - listOf(port.toString()) + listOf(port.toString(), autoacceptOnTrusted.toString()) ) if(processRestarts == 6) { diff --git a/EOCV-Sim/src/main/resources/repository.toml b/EOCV-Sim/src/main/resources/repository.toml index 79e1b2a..2831e8a 100644 --- a/EOCV-Sim/src/main/resources/repository.toml +++ b/EOCV-Sim/src/main/resources/repository.toml @@ -1,10 +1,11 @@ [repositories] -# Declare the URL of the Maven repositories to use, with a friendly name. +# Declare the URL of the Maven repositories to use, with a key name. # The URL must preferably end with a slash to be handled correctly. central = "https://repo.maven.apache.org/maven2/" jitpack = "https://jitpack.io/" [plugins] -# Declare the plugin ID and the Maven coordinates of the plugin, similar to how you do it in Gradle. -# (group:artifact:version) which will be used to download the plugin from one of Maven repositories. -# Any dependency that does not have a plugin.toml in its jar will not be considered after download. \ No newline at end of file +# Declare the ID and the Maven coordinates of the plugin (group:artifact:version) +# which will be used to download the plugin from one of Maven repositories specified above. +# Any dependency that does not have a plugin.toml in its jar will not be considered after download. +PaperVision = "org.deltacv.PaperVision:EOCVSimPlugin:1.0.0" \ No newline at end of file