mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 16:20:03 +00:00
Minor refactoring
Added UNTRUSTED trust level for plugins with invalid signature
This commit is contained in:
@@ -81,6 +81,10 @@ export function PluginManagementCard({plugin, updatePlugin}: {
|
|||||||
return <Tooltip color="foreground" placement="bottom" content="3rd party plugin">
|
return <Tooltip color="foreground" placement="bottom" content="3rd party plugin">
|
||||||
<SealWarning/>
|
<SealWarning/>
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
|
case PluginTrustLevel.UNTRUSTED:
|
||||||
|
return <Tooltip color="foreground" placement="bottom" content="Plugin verification failed">
|
||||||
|
<SealWarning weight="fill" className="fill-danger"/>
|
||||||
|
</Tooltip>;
|
||||||
default:
|
default:
|
||||||
return <Tooltip color="foreground" placement="bottom" content="Unkown verification status">
|
return <Tooltip color="foreground" placement="bottom" content="Unkown verification status">
|
||||||
<SealQuestion/>
|
<SealQuestion/>
|
||||||
|
|||||||
+30
-18
@@ -31,6 +31,7 @@ class GameyfinPluginManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
private val publicKey: PublicKey = loadPluginSignaturePublicKey()
|
||||||
|
|
||||||
// This took me way too long to figure out...
|
// This took me way too long to figure out...
|
||||||
// But I learned a lot about Kotlin and Java interoperability in the process
|
// But I learned a lot about Kotlin and Java interoperability in the process
|
||||||
@@ -66,36 +67,45 @@ class GameyfinPluginManager(
|
|||||||
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
||||||
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
||||||
|
|
||||||
if (pluginWrapper != null) {
|
if (pluginWrapper == null || pluginPath == null) return null
|
||||||
|
|
||||||
// Inject config after loading, before starting
|
// Inject config after loading, before starting
|
||||||
configurePlugin(pluginWrapper)
|
configurePlugin(pluginWrapper)
|
||||||
|
|
||||||
// Update or create the PluginManagementEntry
|
var pluginManagementEntry = pluginManagementRepository.findByIdOrNull(pluginWrapper.pluginId)
|
||||||
if (pluginPath != null) {
|
|
||||||
|
if (pluginManagementEntry == null) {
|
||||||
|
// Create a new entry
|
||||||
|
|
||||||
// Set priority to the max value of the current plugins + 1 (which is the lowest priority) or 1 if there are no entries
|
// Set priority to the max value of the current plugins + 1 (which is the lowest priority) or 1 if there are no entries
|
||||||
val currentMaxPriority = pluginManagementRepository.findMaxPriority() ?: 0
|
val currentMaxPriority = pluginManagementRepository.findMaxPriority() ?: 0
|
||||||
|
|
||||||
val pluginManagementEntry = pluginManagementRepository.findByIdOrNull(pluginWrapper.pluginId)
|
pluginManagementEntry =
|
||||||
?: PluginManagementEntry(pluginId = pluginWrapper.pluginId, priority = currentMaxPriority + 1)
|
PluginManagementEntry(pluginId = pluginWrapper.pluginId, priority = currentMaxPriority + 1)
|
||||||
|
|
||||||
if (pluginPath.extension == "jar") {
|
pluginManagementEntry.trustLevel = when (pluginPath.extension) {
|
||||||
log.debug { "Verifying plugin signature for ${pluginWrapper.pluginId}" }
|
"jar" -> verifyPluginSignature(pluginPath)
|
||||||
pluginManagementEntry.trustLevel = verifyPluginSignature(pluginPath)
|
else -> PluginTrustLevel.BUNDLED
|
||||||
log.debug { "Plugin ${pluginWrapper.pluginId} verification status: ${pluginManagementEntry.trustLevel}" }
|
|
||||||
} else {
|
|
||||||
pluginManagementEntry.trustLevel = PluginTrustLevel.BUNDLED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pluginManagementEntry.trustLevel in listOf(PluginTrustLevel.OFFICIAL, PluginTrustLevel.BUNDLED)) {
|
// If the plugin is official or bundled, we can enable it and start it by default
|
||||||
|
if (pluginManagementEntry.trustLevel == PluginTrustLevel.OFFICIAL
|
||||||
|
|| pluginManagementEntry.trustLevel == PluginTrustLevel.BUNDLED
|
||||||
|
) {
|
||||||
pluginManagementEntry.enabled = true
|
pluginManagementEntry.enabled = true
|
||||||
log.info { "Plugin ${pluginWrapper.pluginId} verified, starting" }
|
log.info { "Plugin ${pluginWrapper.pluginId} verified, starting" }
|
||||||
startPlugin(pluginWrapper.pluginId)
|
startPlugin(pluginWrapper.pluginId)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Just re-verify the plugin if it was already in the database
|
||||||
|
pluginManagementEntry.trustLevel = when (pluginPath.extension) {
|
||||||
|
"jar" -> verifyPluginSignature(pluginPath)
|
||||||
|
else -> PluginTrustLevel.BUNDLED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug { "Plugin ${pluginWrapper.pluginId} verification status: ${pluginManagementEntry.trustLevel}" }
|
||||||
pluginManagementRepository.save(pluginManagementEntry)
|
pluginManagementRepository.save(pluginManagementEntry)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginWrapper
|
return pluginWrapper
|
||||||
}
|
}
|
||||||
@@ -174,13 +184,15 @@ class GameyfinPluginManager(
|
|||||||
return pluginConfigRepository.findAllById_PluginId(pluginId).associate { it.id.key to it.value }
|
return pluginConfigRepository.findAllById_PluginId(pluginId).associate { it.id.key to it.value }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun verifyPluginSignature(pluginPath: Path): PluginTrustLevel {
|
private fun loadPluginSignaturePublicKey(): PublicKey {
|
||||||
val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
val certFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
val certFileInputStream = javaClass.classLoader.getResourceAsStream(PUBLIC_KEY_FILE)
|
val certFileInputStream = javaClass.classLoader.getResourceAsStream(PUBLIC_KEY_FILE)
|
||||||
val cert: X509Certificate = certFactory.generateCertificate(certFileInputStream) as X509Certificate
|
val cert: X509Certificate = certFactory.generateCertificate(certFileInputStream) as X509Certificate
|
||||||
val publicKey: PublicKey = cert.publicKey
|
|
||||||
certFileInputStream?.close()
|
certFileInputStream?.close()
|
||||||
|
return cert.publicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyPluginSignature(pluginPath: Path): PluginTrustLevel {
|
||||||
val jarFile = JarFile(pluginPath.toFile(), true)
|
val jarFile = JarFile(pluginPath.toFile(), true)
|
||||||
val entries = jarFile.entries()
|
val entries = jarFile.entries()
|
||||||
|
|
||||||
@@ -197,7 +209,7 @@ class GameyfinPluginManager(
|
|||||||
}
|
}
|
||||||
} catch (_: SecurityException) {
|
} catch (_: SecurityException) {
|
||||||
// Signature verification failed
|
// Signature verification failed
|
||||||
return PluginTrustLevel.THIRD_PARTY
|
return PluginTrustLevel.UNTRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
val codeSigners = entry.codeSigners
|
val codeSigners = entry.codeSigners
|
||||||
@@ -216,7 +228,7 @@ class GameyfinPluginManager(
|
|||||||
cert.verify(publicKey)
|
cert.verify(publicKey)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
// Signature verification failed
|
// Signature verification failed
|
||||||
return PluginTrustLevel.THIRD_PARTY
|
return PluginTrustLevel.UNTRUSTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -4,5 +4,6 @@ enum class PluginTrustLevel {
|
|||||||
BUNDLED,
|
BUNDLED,
|
||||||
OFFICIAL,
|
OFFICIAL,
|
||||||
THIRD_PARTY,
|
THIRD_PARTY,
|
||||||
|
UNTRUSTED,
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ function statsExtracterPlugin(): PluginOption {
|
|||||||
const frontendFiles: Record<string, string> = {};
|
const frontendFiles: Record<string, string> = {};
|
||||||
frontendFiles['index.html'] = createHash('sha256').update(customIndexData.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
|
frontendFiles['index.html'] = createHash('sha256').update(customIndexData.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
|
||||||
|
|
||||||
const projectFileExtensions = ['.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map'];
|
const projectFileExtensions = ['.js', '.js.map', '.ts', '.ts.map', '.tsx', '.tsx.map', '.css', '.css.map', '.'];
|
||||||
|
|
||||||
const isThemeComponentsResource = (id: string) =>
|
const isThemeComponentsResource = (id: string) =>
|
||||||
id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
|
id.startsWith(themeOptions.frontendGeneratedFolder.replace(/\\/g, '/'))
|
||||||
|
|||||||
Reference in New Issue
Block a user