possible fix for remotely loaded plugins

This commit is contained in:
RaidMax 2023-05-27 12:15:22 -05:00
parent 35f9eb5933
commit d6d2717771
2 changed files with 93 additions and 76 deletions

View File

@ -13,10 +13,10 @@ namespace IW4MAdmin.Application.Misc
{ {
public class RemoteAssemblyHandler : IRemoteAssemblyHandler public class RemoteAssemblyHandler : IRemoteAssemblyHandler
{ {
private const int keyLength = 32; private const int KeyLength = 32;
private const int tagLength = 16; private const int TagLength = 16;
private const int nonceLength = 12; private const int NonceLength = 12;
private const int iterationCount = 10000; private const int IterationCount = 10000;
private readonly ApplicationConfiguration _appconfig; private readonly ApplicationConfiguration _appconfig;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.Misc
public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies) public IEnumerable<Assembly> DecryptAssemblies(string[] encryptedAssemblies)
{ {
return DecryptContent(encryptedAssemblies) return DecryptContent(encryptedAssemblies)
.Select(decryptedAssembly => Assembly.Load(decryptedAssembly)); .Select(Assembly.Load);
} }
public IEnumerable<string> DecryptScripts(string[] encryptedScripts) public IEnumerable<string> DecryptScripts(string[] encryptedScripts)
@ -38,24 +38,24 @@ namespace IW4MAdmin.Application.Misc
return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript)); return DecryptContent(encryptedScripts).Select(decryptedScript => Encoding.UTF8.GetString(decryptedScript));
} }
private byte[][] DecryptContent(string[] content) private IEnumerable<byte[]> DecryptContent(string[] content)
{ {
if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId)) if (string.IsNullOrEmpty(_appconfig.Id) || string.IsNullOrWhiteSpace(_appconfig.SubscriptionId))
{ {
_logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts"); _logger.LogWarning($"{nameof(_appconfig.Id)} and {nameof(_appconfig.SubscriptionId)} must be provided to attempt loading remote assemblies/scripts");
return new byte[0][]; return Array.Empty<byte[]>();
} }
var assemblies = content.Select(piece => var assemblies = content.Select(piece =>
{ {
byte[] byteContent = Convert.FromBase64String(piece); var byteContent = Convert.FromBase64String(piece);
byte[] encryptedContent = byteContent.Take(byteContent.Length - (tagLength + nonceLength)).ToArray(); var encryptedContent = byteContent.Take(byteContent.Length - (TagLength + NonceLength)).ToArray();
byte[] tag = byteContent.Skip(byteContent.Length - (tagLength + nonceLength)).Take(tagLength).ToArray(); var tag = byteContent.Skip(byteContent.Length - (TagLength + NonceLength)).Take(TagLength).ToArray();
byte[] nonce = byteContent.Skip(byteContent.Length - nonceLength).Take(nonceLength).ToArray(); var nonce = byteContent.Skip(byteContent.Length - NonceLength).Take(NonceLength).ToArray();
byte[] decryptedContent = new byte[encryptedContent.Length]; var decryptedContent = new byte[encryptedContent.Length];
var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id.ToString()), iterationCount, HashAlgorithmName.SHA512); var keyGen = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(_appconfig.SubscriptionId), Encoding.UTF8.GetBytes(_appconfig.Id), IterationCount, HashAlgorithmName.SHA512);
var encryption = new AesGcm(keyGen.GetBytes(keyLength)); var encryption = new AesGcm(keyGen.GetBytes(KeyLength));
try try
{ {

View File

@ -20,13 +20,21 @@ namespace IW4MAdmin.Application.Plugin
public class PluginImporter : IPluginImporter public class PluginImporter : IPluginImporter
{ {
private IEnumerable<PluginSubscriptionContent> _pluginSubscription; private IEnumerable<PluginSubscriptionContent> _pluginSubscription;
private static readonly string PluginDir = "Plugins"; private const string PluginDir = "Plugins";
private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init"; private const string PluginV2Match = "^ *((?:var|const|let) +init)|function init";
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IRemoteAssemblyHandler _remoteAssemblyHandler; private readonly IRemoteAssemblyHandler _remoteAssemblyHandler;
private readonly IMasterApi _masterApi; private readonly IMasterApi _masterApi;
private readonly ApplicationConfiguration _appConfig; private readonly ApplicationConfiguration _appConfig;
private static readonly Type[] FilterTypes =
{
typeof(IPlugin),
typeof(IPluginV2),
typeof(Command),
typeof(IBaseConfiguration)
};
public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi, public PluginImporter(ILogger<PluginImporter> logger, ApplicationConfiguration appConfig, IMasterApi masterApi,
IRemoteAssemblyHandler remoteAssemblyHandler) IRemoteAssemblyHandler remoteAssemblyHandler)
{ {
@ -77,74 +85,80 @@ namespace IW4MAdmin.Application.Plugin
public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations() public (IEnumerable<Type>, IEnumerable<Type>, IEnumerable<Type>) DiscoverAssemblyPluginImplementations()
{ {
var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}"; var pluginDir = $"{Utilities.OperatingDirectory}{PluginDir}{Path.DirectorySeparatorChar}";
var pluginTypes = Enumerable.Empty<Type>(); var pluginTypes = new List<Type>();
var commandTypes = Enumerable.Empty<Type>(); var commandTypes = new List<Type>();
var configurationTypes = Enumerable.Empty<Type>(); var configurationTypes = new List<Type>();
if (Directory.Exists(pluginDir)) if (!Directory.Exists(pluginDir))
{ {
var dllFileNames = Directory.GetFiles(pluginDir, "*.dll"); return (pluginTypes, commandTypes, configurationTypes);
_logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length); }
if (dllFileNames.Length > 0) var dllFileNames = Directory.GetFiles(pluginDir, "*.dll");
_logger.LogDebug("Discovered {Count} potential plugin assemblies", dllFileNames.Length);
if (!dllFileNames.Any())
{
return (pluginTypes, commandTypes, configurationTypes);
}
// we only want to load the most recent assembly in case of duplicates
var assemblies = dllFileNames.Select(Assembly.LoadFrom)
.Union(GetRemoteAssemblies())
.GroupBy(assembly => assembly.FullName).Select(assembly =>
assembly.OrderByDescending(asm => asm.GetName().Version).First());
var eligibleAssemblyTypes = assemblies
.SelectMany(asm =>
{ {
// we only want to load the most recent assembly in case of duplicates try
var assemblies = dllFileNames.Select(name => Assembly.LoadFrom(name)) {
.Union(GetRemoteAssemblies()) return asm.GetTypes();
.GroupBy(assembly => assembly.FullName).Select(assembly => assembly.OrderByDescending(asm => asm.GetName().Version).First()); }
catch
{
return Enumerable.Empty<Type>();
}
}).Where(type =>
FilterTypes.Any(filterType => type.GetInterface(filterType.Name, false) != null) ||
(type.IsClass && FilterTypes.Contains(type.BaseType)));
pluginTypes = assemblies foreach (var assemblyType in eligibleAssemblyTypes)
.SelectMany(asm => {
{ var isPlugin =
try (assemblyType.GetInterface(nameof(IPlugin), false) ??
{ assemblyType.GetInterface(nameof(IPluginV2), false)) != null &&
return asm.GetTypes(); (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(assemblyType => (assemblyType.GetInterface(nameof(IPlugin), false) ?? assemblyType.GetInterface(nameof(IPluginV2), false)) != null)
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
_logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count()); if (isPlugin)
{
pluginTypes.Add(assemblyType);
continue;
}
commandTypes = assemblies var isCommand = assemblyType.IsClass && assemblyType.BaseType == typeof(Command) &&
.SelectMany(asm =>{ (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
try
{
return asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(assemblyType => assemblyType.IsClass && assemblyType.BaseType == typeof(Command))
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
_logger.LogDebug("Discovered {Count} plugin commands", commandTypes.Count()); if (isCommand)
{
commandTypes.Add(assemblyType);
continue;
}
configurationTypes = assemblies var isConfiguration = assemblyType.IsClass &&
.SelectMany(asm => { assemblyType.GetInterface(nameof(IBaseConfiguration), false) != null &&
try (!assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
{
return asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(asmType =>
asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null)
.Where(assemblyType => !assemblyType.Namespace?.StartsWith(nameof(SharedLibraryCore)) ?? false);
_logger.LogDebug("Discovered {Count} configuration implementations", configurationTypes.Count()); if (isConfiguration)
{
configurationTypes.Add(assemblyType);
} }
} }
_logger.LogDebug("Discovered {Count} plugin implementations", pluginTypes.Count);
_logger.LogDebug("Discovered {Count} plugin commands", pluginTypes.Count);
_logger.LogDebug("Discovered {Count} configuration implementations", pluginTypes.Count);
return (pluginTypes, commandTypes, configurationTypes); return (pluginTypes, commandTypes, configurationTypes);
} }
@ -152,10 +166,11 @@ namespace IW4MAdmin.Application.Plugin
{ {
try try
{ {
if (_pluginSubscription == null) _pluginSubscription ??= _masterApi
_pluginSubscription = _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; .GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray()); return _remoteAssemblyHandler.DecryptAssemblies(_pluginSubscription
.Where(sub => sub.Type == PluginType.Binary).Select(sub => sub.Content).ToArray());
} }
catch (Exception ex) catch (Exception ex)
@ -169,9 +184,11 @@ namespace IW4MAdmin.Application.Plugin
{ {
try try
{ {
_pluginSubscription ??= _masterApi.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result; _pluginSubscription ??= _masterApi
.GetPluginSubscription(Guid.Parse(_appConfig.Id), _appConfig.SubscriptionId).Result;
return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray()); return _remoteAssemblyHandler.DecryptScripts(_pluginSubscription
.Where(sub => sub.Type == PluginType.Script).Select(sub => sub.Content).ToArray());
} }
catch (Exception ex) catch (Exception ex)