feat(license): implement Hardware-ID v2, multi-platform Linux support, StateStore LLS2 hardening, and AI Agent docs

This commit is contained in:
Deploymentcenter Bot
2026-08-06 11:39:21 +02:00
parent e9dbe793e2
commit 70b35f7b8b
16 changed files with 1602 additions and 41 deletions
@@ -0,0 +1,348 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Win32;
namespace Deploymentcenter.Client;
public class HardwareIdResult
{
public string HardwareId { get; set; } = string.Empty;
public int HwidVersion { get; set; } = 2;
public string HwidSource { get; set; } = string.Empty;
public string Platform { get; set; } = string.Empty;
public string LegacyHardwareId { get; set; } = string.Empty;
}
public static class HardwareId
{
private static readonly string[] MacStopWords = new[]
{
"docker", "veth", "br-", "virbr", "cni", "flannel", "cali", "weave",
"zt", "tailscale", "ipsec", "sit", "gre", "dummy", "bond", "macvlan", "ovs"
};
public static HardwareIdResult GetHardwareId(string productSlug = "default_app")
{
string platform = OperatingSystemHelpers.GetPlatformCode();
string rawValue = string.Empty;
string source = string.Empty;
// 3.1 Override (all platforms, highest priority)
var overrideValue = LicenseConfig.HardwareIdOverride;
if (!string.IsNullOrWhiteSpace(overrideValue))
{
rawValue = overrideValue!.Trim();
source = "override";
}
else if (OperatingSystemHelpers.IsWindows())
{
// 3.2 Windows Sources
string? machineGuid = GetWindowsMachineGuid();
if (IsPlausibleMachineId(machineGuid))
{
rawValue = machineGuid!;
source = "machine-guid";
}
else
{
string? macs = GetStablePhysicalMacs();
if (!string.IsNullOrWhiteSpace(macs))
{
rawValue = macs!;
source = "mac";
}
else
{
rawValue = GetOrCreateKeyfile(productSlug);
source = "keyfile";
}
}
}
else if (OperatingSystemHelpers.IsLinux())
{
// 3.3 Linux Sources
string? systemdId = ReadTextFileFile("/etc/machine-id");
if (IsPlausibleMachineId(systemdId))
{
rawValue = systemdId!;
source = "machine-id";
}
else
{
string? dbusId = ReadTextFileFile("/var/lib/dbus/machine-id");
if (IsPlausibleMachineId(dbusId))
{
rawValue = dbusId!;
source = "dbus-machine-id";
}
else
{
string? dmiUuid = ReadTextFileFile("/sys/class/dmi/id/product_uuid");
if (IsPlausibleMachineId(dmiUuid))
{
rawValue = dmiUuid!;
source = "dmi-uuid";
}
else
{
string? macs = GetStablePhysicalMacs();
if (!string.IsNullOrWhiteSpace(macs))
{
rawValue = macs!;
source = "mac";
}
else
{
rawValue = GetOrCreateKeyfile(productSlug);
source = "keyfile";
}
}
}
}
}
else
{
// Generic fallback
string? macs = GetStablePhysicalMacs();
if (!string.IsNullOrWhiteSpace(macs))
{
rawValue = macs!;
source = "mac";
}
else
{
rawValue = GetOrCreateKeyfile(productSlug);
source = "keyfile";
}
}
string v2Hash = ComputeV2Hash(platform, source, rawValue);
string v2HardwareId = $"2:{platform}:{v2Hash}";
string legacyHardwareId = GetLegacyHardwareId();
return new HardwareIdResult
{
HardwareId = v2HardwareId,
HwidVersion = 2,
HwidSource = source,
Platform = platform,
LegacyHardwareId = legacyHardwareId
};
}
public static string ComputeV2Hash(string platform, string source, string rawValue)
{
string domainString = "LicenseLabrador-HWID-v2";
string payload = $"{domainString}\n{platform}\n{source}\n{rawValue}";
using var sha256 = SHA256.Create();
byte[] bytes = Encoding.UTF8.GetBytes(payload);
byte[] hash = sha256.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
public static string GetLegacyHardwareId()
{
// Legacy v1 algorithm: SHA256 of MachineName + First MAC
string firstMac = GetFirstPhysicalMacLegacy();
string raw = $"{Environment.MachineName}:{firstMac}";
using var sha256 = SHA256.Create();
byte[] bytes = Encoding.UTF8.GetBytes(raw);
byte[] hash = sha256.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
public static bool IsPlausibleMachineId(string? v)
{
if (string.IsNullOrWhiteSpace(v))
return false;
string trimmed = v!.Trim();
if (trimmed.Length < 16)
return false;
if (trimmed.Equals("uninitialized", StringComparison.OrdinalIgnoreCase))
return false;
if (trimmed.Trim('0').Length == 0)
return false;
return true;
}
private static string? GetWindowsMachineGuid()
{
if (!OperatingSystemHelpers.IsWindows())
return null;
try
{
using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var subKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
return subKey?.GetValue("MachineGuid")?.ToString();
}
catch
{
return null;
}
}
private static string? GetStablePhysicalMacs()
{
try
{
var validMacs = new List<string>();
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
if (ni.OperationalStatus != OperationalStatus.Up && ni.OperationalStatus != OperationalStatus.Unknown)
continue;
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback ||
ni.NetworkInterfaceType == NetworkInterfaceType.Tunnel)
continue;
string name = ni.Name.ToLowerInvariant();
string desc = ni.Description.ToLowerInvariant();
if (MacStopWords.Any(sw => name.Contains(sw) || desc.Contains(sw)))
continue;
var physAddr = ni.GetPhysicalAddress();
var bytes = physAddr.GetAddressBytes();
if (bytes.Length == 0)
continue;
// Check locally administered bit (mac[0] & 0x02) != 0
if ((bytes[0] & 0x02) != 0)
continue;
// On Linux, check /sys/class/net/<name>/device
if (OperatingSystemHelpers.IsLinux())
{
string sysDevicePath = $"/sys/class/net/{ni.Name}/device";
if (!Directory.Exists(sysDevicePath) && !File.Exists(sysDevicePath))
continue;
}
string macHex = BitConverter.ToString(bytes).Replace("-", "").ToUpperInvariant();
if (!string.IsNullOrWhiteSpace(macHex) && macHex.Trim('0').Length > 0)
{
validMacs.Add(macHex);
}
}
if (validMacs.Count == 0)
return null;
validMacs.Sort();
return string.Join(":", validMacs);
}
catch
{
return null;
}
}
private static string GetFirstPhysicalMacLegacy()
{
try
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
continue;
var bytes = ni.GetPhysicalAddress().GetAddressBytes();
if (bytes.Length > 0)
{
return BitConverter.ToString(bytes).Replace("-", "").ToUpperInvariant();
}
}
}
catch { }
return "000000000000";
}
private static string? ReadTextFileFile(string path)
{
try
{
if (!File.Exists(path))
return null;
return File.ReadAllText(path).Trim();
}
catch
{
return null;
}
}
private static string GetOrCreateKeyfile(string productSlug)
{
string dir = LicenseConfig.GetStorageDirectory(productSlug);
Directory.CreateDirectory(dir);
string keyfilePath = Path.Combine(dir, "machine.key");
if (File.Exists(keyfilePath))
{
try
{
string existing = File.ReadAllText(keyfilePath).Trim();
if (!string.IsNullOrWhiteSpace(existing))
return existing;
}
catch { }
}
byte[] randomBytes = new byte[32];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(randomBytes);
}
string keyBase64 = Convert.ToBase64String(randomBytes);
File.WriteAllText(keyfilePath, keyBase64, Encoding.UTF8);
// Set 0600 permissions on Linux/macOS
SetUnixPermissions(keyfilePath, "600");
return keyBase64;
}
private static void SetUnixPermissions(string filePath, string mode)
{
if (OperatingSystemHelpers.IsWindows())
return;
#if NET8_0_OR_GREATER
try
{
File.SetUnixFileMode(filePath, UnixFileMode.UserRead | UnixFileMode.UserWrite);
}
catch { }
#else
try
{
var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "chmod",
Arguments = $"{mode} \"{filePath}\"",
UseShellExecute = false,
CreateNoWindow = true
});
proc?.WaitForExit();
}
catch { }
#endif
}
}