Skip to main content

GST Licensing Machine Binding Guide

This document describes the architecture of GST.Core.Licensing, the Machine Fingerprint mechanism, and the deployment workflow.

1. Architecture Overview

┌──────────────────────────────────────────────────┐
│ Metalama Aspect │
│ [LicenseCheck] / [LicenseRequired("feature")] │
├──────────────────────────────────────────────────┤
│ ILicenseService │
│ Validation, Feature checks, USB Dongle, │
│ background monitoring │
├──────────────────────────────────────────────────┤
│ IReactorLicenseProvider │
│ .NET Reactor License.Status wrapper │
├──────────────────────────────────────────────────┤
│ Hardware Layer │
│ MachineInfoService (WMI Fingerprint) │
│ UsbDeviceService (USB Dongle detection) │
└──────────────────────────────────────────────────┘

2. Core Interfaces

2.1 ILicenseService

public interface ILicenseService
{
ILicenseInfo? CurrentLicense { get; }
bool IsLicenseValid();
bool IsFeatureEnabled(string featureCode);
bool IsAnyFeatureEnabled(params string[] featureCodes);
IReadOnlyList<string> GetEnabledFeatures();
int? GetLimit(string limitCode);
bool IsUsbDonglePresent();
Task<LicenseActivationResult> ActivateAsync(string licenseKey);
Task<bool> DeactivateAsync();
}

2.2 ILicenseInfo

public interface ILicenseInfo
{
string LicenseId { get; }
string CustomerName { get; }
LicenseTier Tier { get; } // Trial/Basic/Standard/Professional/Enterprise
DateTime ExpiresAt { get; }
int DaysRemaining { get; }
bool IsExpired { get; }
bool IsTrial { get; }
LicenseBindingMode BindingMode { get; }
}

2.3 LicenseBindingMode

[Flags]
public enum LicenseBindingMode
{
None = 0,
Machine = 1, // Bound to hardware Fingerprint
UsbDongle = 2, // Bound to USB Dongle
MachineOrUsb = 3, // Either one (flexible)
MachineAndUsb = 4, // Both required (strict)
Online = 8 // Requires online verification
}

3. Machine Fingerprint

3.1 Components

ComponentWMI QueryExample
CPU IDWin32_Processor.ProcessorIdBFEBFBFF000A0653
Motherboard SerialWin32_BaseBoard.SerialNumberDBBGV1100120700A379100
BIOS SerialWin32_BIOS.SerialNumberUDBGXTA00K221008170001
HDD SerialWin32_DiskDrive.SerialNumber (Index=0)0000_0000_...
MAC AddressNetworkInterface.GetPhysicalAddress() (highest speed non-Loopback)2C-0D-A7-B4-3B-FB

3.2 Combined Hash

CombinedHash = SHA256(UTF8("{CpuId}|{MotherboardSerial}|{BiosSerial}|{HddSerial}"))

Only non-empty components participate in the Hash calculation.

3.3 Fault Tolerance

public bool Matches(MachineFingerprint other, int tolerance = 1)

By default, 1 mismatched component is tolerated, handling scenarios where hardware is replaced (e.g., replacing a network card or hard drive).

4. USB Dongle Support

4.1 Detection Method

Win32_DiskDrive (InterfaceType='USB')
→ Win32_DiskDriveToDiskPartition
→ Win32_LogicalDiskToPartition
→ Retrieve DriveLetter, SerialNumber, VID/PID

4.2 UsbDeviceInfo

public record UsbDeviceInfo
{
public required string DeviceId { get; init; }
public required string SerialNumber { get; init; }
public string VolumeLabel { get; init; }
public string DriveLetter { get; init; }
public string VendorId { get; init; } // USB VID
public string ProductId { get; init; } // USB PID
}

5. Metalama Aspect Integration

5.1 [LicenseCheck] — Verify License Exists

[LicenseCheck]
public void PerformCriticalOperation() { /* ... */ }
// 無有效 License → 拋出 LicenseException

[LicenseCheck(ThrowOnFailure = false)]
public void PerformOptionalOperation() { /* ... */ }
// 無有效 License → 回傳 default,不拋例外

5.2 [LicenseRequired] — Verify Specific Feature

[LicenseRequired("AdvancedReporting")]
public Report GenerateAdvancedReport() { /* ... */ }
// Feature 未啟用 → 拋出 LicenseException

6. DI Registration

services.AddLicensing(options =>
{
options.LicenseFilePath = Path.Combine(configDir, "license.lic");
options.ThrowOnLicenseFailure = false;
options.BackgroundCheckIntervalMinutes = 30;
options.FingerprintTolerance = 1; // 容許 1 個硬體元件不符
options.AllowOfflineMode = true; // 支援離線環境
});

7. Deployment Workflow

7.1 Fresh Deployment

1. Run the application on the target machine
└→ Application detects no License
└→ Exports hardware-id.txt (containing CombinedHash)

2. User sends hardware-id.txt to the development team

3. Development team uses .NET Reactor License Manager to generate license.lic
└→ Bound to the machine's Hardware ID

4. Place license.lic at %APPDATA%\{AppName}\license.lic

5. Restart the application → Verification passes

7.2 License File Location

%APPDATA%\ProcessVision\license.lic

7.3 App.xaml.cs Loading Sequence

// 1. 複製 License 檔(在 DI 之前,Reactor 保護 DLL 載入前)
CopyLicenseFiles();

// 2. DI 註冊 AddLicensing()

// 3. 初始化 AspectServiceLocator(啟用 Aspect 注入)
AspectServiceLocator.Initialize(Services);

// 4. 驗證 License 狀態
CheckLicenseStatus();

// 5. 若無 License,匯出 Hardware ID
ExportHardwareId();

8. Debug vs Release Behavior

ScenarioDebugRelease
No LicenseSilently allows all featuresShows countdown window / restricts features
ValidatorDevelopmentLicenseValidator (all pass)LicenseValidator (Reactor verification)
Feature CheckAll enabledBased on License KeyValue

9. .NET Reactor Hardware Lock Differences

GST.Core.Licensing.NET Reactor Hardware Lock
LevelApplication layerDLL protection layer
Binding MethodSHA256 FingerprintReactor built-in HID
Fault ToleranceSupported (tolerance parameter)Not supported
Feature ControlSupported (KeyValue)Not supported
PurposeApplication license managementPrevent DLL copy execution

The two are independent mechanisms and can be used simultaneously.

10. Frequently Asked Questions

10.1 License Verification Failure

  1. Confirm license.lic is at the correct path (%APPDATA%\{AppName}\)
  2. Confirm the License has not expired
  3. Confirm hardware has not been significantly replaced (exceeding tolerance)
  4. Check whether hardware-id.txt matches the License

10.2 License Invalidated After Hardware Replacement

Replacing more than 1 hardware component simultaneously (e.g., replacing both the motherboard and hard drive) will cause a Fingerprint mismatch. A new Hardware ID must be exported and a new License generated.

10.3 Offline Environment (Air-Gapped)

ValidateOnlineAsync() currently returns true (no network required), specifically designed for factory offline environments.


Document Version: v1.0 | Created: 2026-03-16 | Linear Issue: GST-172