mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-12-13 19:12:49 +00:00
Merge pull request #365 from gnh1201/dev
Update the integrity check (Catswords.Phantomizer)
This commit is contained in:
commit
2cb422d075
|
|
@ -3,6 +3,9 @@
|
|||
// SPDX-FileCopyrightText: Namhyeon Go <gnh1201@catswords.re.kr>, 2025 Catswords OSS and WelsonJS Contributors
|
||||
// https://github.com/gnh1201/welsonjs
|
||||
//
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
|
@ -33,6 +36,12 @@ namespace Catswords.Phantomizer
|
|||
public static string BaseUrl { get; set; } = null;
|
||||
public static string LoaderNamespace { get; set; } = typeof(AssemblyLoader).Namespace;
|
||||
public static string AppName { get; set; } = "Catswords";
|
||||
public static string IntegrityUrl { get; set; } = null;
|
||||
|
||||
// Hash whitelist (values only)
|
||||
private static HashSet<string> _integrityHashes = null;
|
||||
private static bool _integrityLoaded = false;
|
||||
private static readonly object IntegritySyncRoot = new object();
|
||||
|
||||
private static readonly object SyncRoot = new object();
|
||||
private static bool _registered;
|
||||
|
|
@ -167,16 +176,28 @@ namespace Catswords.Phantomizer
|
|||
if (_registered)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||
try
|
||||
{
|
||||
Trace.TraceError("AssemblyLoader.Register() called but BaseUrl is not set.");
|
||||
throw new InvalidOperationException("AssemblyLoader.BaseUrl must be configured before Register().");
|
||||
}
|
||||
if (!_integrityLoaded)
|
||||
LoadIntegrityManifest();
|
||||
|
||||
if (!BaseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||
throw new InvalidOperationException("BaseUrl must be configured before Register().");
|
||||
|
||||
if (Uri.TryCreate(BaseUrl, UriKind.Absolute, out Uri uri))
|
||||
{
|
||||
if (uri.Scheme != Uri.UriSchemeHttps)
|
||||
throw new InvalidOperationException("BaseUrl must use HTTPS for security.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("BaseUrl is not a valid absolute URI.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceError("AssemblyLoader.BaseUrl must use HTTPS for security.");
|
||||
throw new InvalidOperationException("AssemblyLoader.BaseUrl must use HTTPS.");
|
||||
Trace.TraceError("AssemblyLoader: failed to initialize: {0}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||
|
|
@ -236,6 +257,7 @@ namespace Catswords.Phantomizer
|
|||
Trace.TraceInformation("Using cached native module: {0}", localPath);
|
||||
}
|
||||
|
||||
EnsureIntegrityOrThrow(localPath);
|
||||
EnsureSignedFileOrThrow(localPath, fileName);
|
||||
|
||||
IntPtr h = LoadLibrary(localPath);
|
||||
|
|
@ -324,6 +346,7 @@ namespace Catswords.Phantomizer
|
|||
return null;
|
||||
}
|
||||
|
||||
EnsureIntegrityOrThrow(dllPath);
|
||||
EnsureSignedFileOrThrow(dllPath, simpleName);
|
||||
return Assembly.LoadFrom(dllPath);
|
||||
}
|
||||
|
|
@ -447,14 +470,85 @@ namespace Catswords.Phantomizer
|
|||
|
||||
private static bool IsFrameworkAssembly(string name)
|
||||
{
|
||||
return name.StartsWith("System", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) ||
|
||||
return name.StartsWith("System.", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase) ||
|
||||
name == "mscorlib" ||
|
||||
name == "netstandard" ||
|
||||
name == "WindowsBase" ||
|
||||
name == "PresentationCore" ||
|
||||
name == "PresentationFramework" ||
|
||||
name.StartsWith(LoaderNamespace, StringComparison.OrdinalIgnoreCase);
|
||||
name.StartsWith($"{LoaderNamespace}.", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void LoadIntegrityManifest()
|
||||
{
|
||||
lock (IntegritySyncRoot)
|
||||
{
|
||||
if (_integrityLoaded)
|
||||
return;
|
||||
|
||||
_integrityHashes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(IntegrityUrl))
|
||||
{
|
||||
_integrityLoaded = true;
|
||||
return; // integrity disabled
|
||||
}
|
||||
|
||||
XDocument doc;
|
||||
|
||||
try
|
||||
{
|
||||
if (Uri.TryCreate(IntegrityUrl, UriKind.Absolute, out Uri uri))
|
||||
{
|
||||
if (uri.Scheme != Uri.UriSchemeHttps)
|
||||
throw new InvalidOperationException("IntegrityUrl must use HTTPS for security.");
|
||||
|
||||
using (var res = Http.GetAsync(uri).GetAwaiter().GetResult())
|
||||
{
|
||||
res.EnsureSuccessStatusCode();
|
||||
using (var stream = res.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
|
||||
{
|
||||
doc = XDocument.Load(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("IntegrityUrl is not a valid absolute URI.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceError("AssemblyIntegrity: failed to load manifest: {0}", ex.Message);
|
||||
throw new InvalidOperationException("Failed to load AssemblyIntegrity manifest.", ex);
|
||||
}
|
||||
|
||||
XElement hashes = doc.Root?.Element("Hashes");
|
||||
if (hashes == null)
|
||||
{
|
||||
Trace.TraceWarning("AssemblyIntegrity: <Hashes> not found. Integrity disabled.");
|
||||
_integrityLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var h in hashes.Elements("Hash"))
|
||||
{
|
||||
var algorithm = h.Attribute("algorithm")?.Value?.Trim();
|
||||
|
||||
if (!string.Equals(algorithm, "SHA256", StringComparison.OrdinalIgnoreCase))
|
||||
continue; // only SHA256 supported
|
||||
|
||||
string val = h.Attribute("value")?.Value?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(val))
|
||||
continue;
|
||||
|
||||
_integrityHashes.Add(val);
|
||||
}
|
||||
|
||||
_integrityLoaded = true;
|
||||
Trace.TraceInformation("AssemblyIntegrity: loaded {0} allowed hashes.", _integrityHashes.Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -484,6 +578,32 @@ namespace Catswords.Phantomizer
|
|||
throw new InvalidOperationException("Invalid signature: " + logicalName);
|
||||
}
|
||||
|
||||
private static void EnsureIntegrityOrThrow(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(IntegrityUrl))
|
||||
return; // disabled
|
||||
|
||||
if (_integrityHashes == null || _integrityHashes.Count == 0)
|
||||
{
|
||||
Trace.TraceWarning("AssemblyIntegrity: no hashes loaded → skipping check.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = File.ReadAllBytes(path);
|
||||
|
||||
// Compute hashes
|
||||
string sha256 = ComputeHashHex(bytes, SHA256.Create());
|
||||
|
||||
// Check match
|
||||
if (_integrityHashes.Contains(sha256))
|
||||
{
|
||||
Trace.TraceInformation("AssemblyIntegrity: hash OK for {0}", Path.GetFileName(path));
|
||||
return;
|
||||
}
|
||||
|
||||
Trace.TraceError("AssemblyIntegrity: hash mismatch! SHA256={0}", sha256);
|
||||
throw new InvalidOperationException("AssemblyIntegrity check failed for: " + path);
|
||||
}
|
||||
|
||||
private static FileSignatureStatus VerifySignature(string file)
|
||||
{
|
||||
|
|
@ -502,5 +622,17 @@ namespace Catswords.Phantomizer
|
|||
|
||||
return FileSignatureStatus.Invalid;
|
||||
}
|
||||
|
||||
private static string ComputeHashHex(byte[] data, HashAlgorithm algorithm)
|
||||
{
|
||||
using (algorithm)
|
||||
{
|
||||
var hash = algorithm.ComputeHash(data);
|
||||
var sb = new StringBuilder(hash.Length * 2);
|
||||
foreach (var b in hash)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Phantomizer
|
||||
# Catswords.Phantomizer
|
||||
|
||||
**Catswords.Phantomizer** is an HTTP-based dynamic-link library (DLL) loader designed for .NET applications.
|
||||
It allows your application to fetch and load assemblies directly from your CDN (Azure Blob, S3, Cloudflare R2, etc.) at runtime, with optional GZip compression support.
|
||||
|
|
@ -9,12 +9,13 @@ It allows your application to fetch and load assemblies directly from your CDN (
|
|||
|
||||
## 🚀 Features
|
||||
|
||||
* Load managed (`*.dll`) and native (`*.dll`) assemblies over HTTP **(HTTPS only)**
|
||||
* Load managed (`*.dll`) and native (`*.dll`) assemblies over **HTTPS only**
|
||||
* Optional `.dll.gz` decompression for faster network delivery
|
||||
* CDN-friendly URL structure
|
||||
* Easy bootstrap through a small embedded loader
|
||||
* Loader is implemented using **pure .NET BCL only**, ensuring stable operation without external dependencies
|
||||
* Loader is implemented using **pure .NET BCL only** without external dependencies (.NET Fx/Core fully supported)
|
||||
* Built-in **code-signing verification** support to ensure assemblies are trusted and tamper-free
|
||||
* An efficient integrity verification process based on an integrity manifest (NFT-grade immutability)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -67,6 +68,7 @@ private static void InitializeAssemblyLoader()
|
|||
Type loaderType = phantomAsm.GetType("Catswords.Phantomizer.AssemblyLoader", true);
|
||||
|
||||
loaderType.GetProperty("BaseUrl")?.SetValue(null, GetAppConfig("AssemblyBaseUrl")); // Set the CDN base URL
|
||||
//loaderType.GetProperty("IntegrityUrl")?.SetValue(null, GetAppConfig("IntegrityUrl")); // (Optional) Set the integrity URL
|
||||
loaderType.GetProperty("LoaderNamespace")?.SetValue(null, typeof(Program).Namespace);
|
||||
loaderType.GetProperty("AppName")?.SetValue(null, "WelsonJS"); // Application name
|
||||
loaderType.GetMethod("Register")?.Invoke(null, null);
|
||||
|
|
@ -99,6 +101,7 @@ using Catswords.Phantomizer;
|
|||
static void Main(string[] args)
|
||||
{
|
||||
AssemblyLoader.BaseUrl = GetAppConfig("AssemblyBaseUrl"); // Configure CDN base URL
|
||||
//AssemblyLoader.IntegrityUrl // (Optional) Set the integrity URL
|
||||
AssemblyLoader.LoaderNamespace = typeof(Program).Namespace;
|
||||
AssemblyLoader.AppName = "WelsonJS";
|
||||
AssemblyLoader.Register();
|
||||
|
|
@ -134,7 +137,47 @@ Once Phantomizer is initialized, your application will automatically fetch missi
|
|||
|
||||
---
|
||||
|
||||
## Download the pre-compiled file
|
||||
## 🛡 Integrity Manifest (Integrity URL)
|
||||
|
||||
Phantomizer can verify assemblies before loading them by downloading an integrity manifest (XML).
|
||||
|
||||
You can host this integrity file anywhere — **preferably separate from your main CDN**, to prevent tampering and ensure independent verification of assembly integrity.
|
||||
|
||||
### 🔒 Why separate Integrity URL and main CDN?
|
||||
|
||||
Separating them prevents a compromised CDN bucket from serving malicious DLLs **and falsifying the integrity file**. Phantomizer can **trust the integrity manifest**, even if the main CDN is partially compromised.
|
||||
|
||||
### ✔ Recommended: Filebase (IPFS-pinning, NFT-grade immutability)
|
||||
|
||||
Filebase provides **immutable IPFS-based storage**, which is widely used in blockchain ecosystems — including **NFT metadata storage** — due to its strong guarantees of *content-addressing* and *tamper resistance*.
|
||||
Once uploaded and pinned, the file cannot be silently modified without changing its IPFS hash (CID), making it ideal for hosting integrity manifests.
|
||||
|
||||
👉 **Recommended signup (with pinning support):** [Filebase](https://console.filebase.com/signup?ref=d44f5cc9cff7)
|
||||
|
||||
### ✔ Integrity Manifest Example (from `integrity.xml`)
|
||||
|
||||
```xml
|
||||
<AssemblyIntegrity schemaVersion="1" generatedAt="2025-12-10T00:00:00Z">
|
||||
<Hashes>
|
||||
<Hash
|
||||
value="5e274b47fc60c74159b4d1e21e70c0edf8e0936bdabc46b632525d09ca2fbae8"
|
||||
algorithm="SHA256"
|
||||
assemblyName="ChakraCore"
|
||||
assemblyType="native"
|
||||
version="1.13.0.0"
|
||||
platform="x86"
|
||||
compression="none"
|
||||
fileName="ChakraCore.dll" />
|
||||
|
||||
<!-- ... more entries ... -->
|
||||
</Hashes>
|
||||
</AssemblyIntegrity>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📥 Download the pre-compiled file
|
||||
|
||||
* [Download Catswords.Phantomizer.dll.gz (catswords.blob.core.windows.net)](https://catswords.blob.core.windows.net/welsonjs/packages/managed/Catswords.Phantomizer/1.0.0.0/Catswords.Phantomizer.dll.gz)
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ namespace WelsonJS.Launcher
|
|||
loaderType.GetProperty("BaseUrl")?.SetValue(null, GetAppConfig("AssemblyBaseUrl"));
|
||||
loaderType.GetProperty("LoaderNamespace")?.SetValue(null, typeof(Program).Namespace);
|
||||
loaderType.GetProperty("AppName")?.SetValue(null, "WelsonJS");
|
||||
//loaderType.GetProperty("IntegrityUrl")?.SetValue(null, GetAppConfig("AssemblyIntegrityUrl")); // In the future, we may use this to verify integrity.
|
||||
loaderType.GetMethod("Register")?.Invoke(null, null);
|
||||
|
||||
var loadNativeModulesMethod = loaderType.GetMethod(
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@ namespace WelsonJS.Launcher.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
internal static string AssemblyIntegrityUrl {
|
||||
get {
|
||||
return ResourceManager.GetString("AssemblyIntegrityUrl", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -244,4 +244,7 @@
|
|||
<data name="Phantomizer" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Catswords.Phantomizer.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="AssemblyIntegrityUrl" xml:space="preserve">
|
||||
<value>https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
<add key="TelemetryBaseUrl" value="https://us.i.posthog.com"/>
|
||||
<add key="TelemetryEnabled" value="true"/>
|
||||
<add key="AssemblyBaseUrl" value="https://catswords.blob.core.windows.net/welsonjs/packages"/>
|
||||
<add key="AssemblyIntegrityUrl" value="https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf"/>
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user