Files
CertifiEd.Client/HwFingerprint.cs
T
Faris Laptop 49725ddad5 feat: initial CertifiEd licensing platform
Self-hosted, multitenant licensing platform for on-prem software.

- Backend: .NET 10 Clean Architecture (Domain/Application/Infrastructure/Api),
  PostgreSQL 16 with ltree company tree, Ed25519 signing (NSec), cookie auth
  with Argon2id, role enforcement (Operator/Owner/Admin/Viewer over subtree),
  webhooks, audit log, rate limiting.
- Panel: Next.js 15 enterprise admin (RU/EN/TG), Ofarandagon light theme,
  command palette, license issue wizard, server-side pagination.
- Landing: Next.js 15 static export, awwwards-grade, RU/EN/TG + full SEO.
- Client SDK (CertifiEd.Client) with offline Ed25519 validation, heartbeat,
  feature gating; QuickStart example; usage docs.
2026-07-16 19:35:01 +05:00

125 lines
3.9 KiB
C#

using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace CertifiEd.Client;
/// <summary>
/// Computes a stable hardware fingerprint for the current machine — a SHA-256 hex
/// digest over machine name, OS description, the first stable MAC address, and the
/// platform machine id when available.
/// </summary>
public static class HwFingerprint
{
private static string? _cached;
/// <summary>Returns the fingerprint, computed once per process.</summary>
public static string Get()
{
if (_cached is not null)
return _cached;
var raw = new StringBuilder()
.Append(Environment.MachineName).Append('|')
.Append(RuntimeInformation.OSDescription).Append('|')
.Append(GetStableMac() ?? "no-mac");
var platformId = GetPlatformMachineId();
if (platformId is not null)
raw.Append('|').Append(platformId);
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(raw.ToString()));
_cached = Convert.ToHexStringLower(hash);
return _cached;
}
private static string? GetStableMac()
{
try
{
return NetworkInterface.GetAllNetworkInterfaces()
.Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback
&& n.OperationalStatus == OperationalStatus.Up)
.Select(n => n.GetPhysicalAddress().ToString())
.Where(mac => mac.Length > 0)
.Order(StringComparer.Ordinal)
.FirstOrDefault();
}
catch (NetworkInformationException)
{
return null;
}
}
private static string? GetPlatformMachineId()
{
if (OperatingSystem.IsWindows())
return GetWindowsMachineGuid();
if (OperatingSystem.IsLinux())
return TryReadFile("/etc/machine-id") ?? TryReadFile("/var/lib/dbus/machine-id");
if (OperatingSystem.IsMacOS())
return GetMacSerialNumber();
return null;
}
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
private static string? GetWindowsMachineGuid()
{
try
{
using var key = Microsoft.Win32.Registry.LocalMachine
.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
return key?.GetValue("MachineGuid")?.ToString();
}
catch (Exception ex) when (ex is System.Security.SecurityException or UnauthorizedAccessException or IOException)
{
return null;
}
}
private static string? TryReadFile(string path)
{
try
{
return File.Exists(path) ? File.ReadAllText(path).Trim() : null;
}
catch (IOException)
{
return null;
}
catch (UnauthorizedAccessException)
{
return null;
}
}
private static string? GetMacSerialNumber()
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo("ioreg", "-l")
{
RedirectStandardOutput = true,
UseShellExecute = false,
};
using var process = System.Diagnostics.Process.Start(psi);
var output = process?.StandardOutput.ReadToEnd() ?? string.Empty;
var line = output.Split('\n').FirstOrDefault(l => l.Contains("IOPlatformSerialNumber", StringComparison.Ordinal));
if (line is null)
return null;
var start = line.IndexOf('"', line.IndexOf('=', StringComparison.Ordinal) + 1);
var end = start >= 0 ? line.IndexOf('"', start + 1) : -1;
return start >= 0 && end > start ? line[(start + 1)..end] : null;
}
catch (Exception ex) when (ex is System.ComponentModel.Win32Exception or InvalidOperationException or IOException)
{
return null;
}
}
}