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,206 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Deploymentcenter.Client;
public class LicenseValidationResult
{
public bool IsValid { get; set; }
public string Status { get; set; } = "unknown";
public string Message { get; set; } = string.Empty;
public string HardwareId { get; set; } = string.Empty;
public bool IsCached { get; set; }
public long? ExpiresAt { get; set; }
}
public class LicenseClient
{
private readonly HttpClient _httpClient;
private readonly ILicensePrompt _prompt;
public LicenseClient(HttpClient? httpClient = null, ILicensePrompt? prompt = null)
{
_httpClient = httpClient ?? new HttpClient();
_prompt = prompt ?? new ConsoleLicensePrompt();
}
public async Task<LicenseValidationResult> ValidateAsync(string productSlug, string licenseKey, string serverBaseUrl)
{
var hwInfo = HardwareId.GetHardwareId(productSlug);
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
try
{
var payload = new
{
product = productSlug,
license_key = licenseKey,
hardware_id = hwInfo.HardwareId,
legacy_hardware_id = hwInfo.LegacyHardwareId,
hwid_version = hwInfo.HwidVersion,
hwid_source = hwInfo.HwidSource,
platform = hwInfo.Platform,
hostname = Environment.MachineName,
app_version = "1.0.0",
nonce = Guid.NewGuid().ToString("N")
};
string jsonStr = JsonSerializer.Serialize(payload);
var content = new StringContent(jsonStr, Encoding.UTF8, "application/json");
string endpoint = $"{serverBaseUrl.TrimEnd('/')}/api/license/v1/validate";
HttpResponseMessage response = await _httpClient.PostAsync(endpoint, content);
if (response.IsSuccessStatusCode)
{
string resBody = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(resBody);
var root = doc.RootElement;
string status = root.TryGetProperty("status", out var sProp) ? sProp.GetString() ?? "unknown" : "unknown";
string message = root.TryGetProperty("message", out var mProp) ? mProp.GetString() ?? "" : "";
long? expiresAt = root.TryGetProperty("expires_at", out var eProp) && eProp.ValueKind == JsonValueKind.Number ? eProp.GetInt64() : null;
if (status.Equals("valid", StringComparison.OrdinalIgnoreCase))
{
// Save encrypted local cache
var cache = new LocalCacheData
{
SchemaVersion = 2,
ProductSlug = productSlug,
LicenseKey = licenseKey,
HardwareId = hwInfo.HardwareId,
Status = "valid",
IssuedAt = now,
ExpiresAt = expiresAt ?? (now + 7 * 86400),
MaxSeenTime = now,
Checksum = hwInfo.HardwareId
};
StateStore.Save(productSlug, hwInfo.HardwareId, cache);
return new LicenseValidationResult
{
IsValid = true,
Status = status,
Message = message,
HardwareId = hwInfo.HardwareId,
IsCached = false,
ExpiresAt = expiresAt
};
}
else
{
return new LicenseValidationResult
{
IsValid = false,
Status = status,
Message = message,
HardwareId = hwInfo.HardwareId,
IsCached = false
};
}
}
}
catch (Exception ex)
{
// Server request failed -> Fall back to encrypted offline cache
var cache = StateStore.Load(productSlug, hwInfo.HardwareId);
if (cache != null && cache.Status == "valid")
{
// Check time-rollback protection
if (now < cache.MaxSeenTime)
{
return new LicenseValidationResult
{
IsValid = false,
Status = "clock_rollback",
Message = "System clock rollback detected! Online verification required.",
HardwareId = hwInfo.HardwareId
};
}
// Check cache expiry
if (cache.ExpiresAt > 0 && now > cache.ExpiresAt)
{
return new LicenseValidationResult
{
IsValid = false,
Status = "cache_expired",
Message = "Cached license has expired.",
HardwareId = hwInfo.HardwareId
};
}
// Update max_seen_time
cache.MaxSeenTime = now;
StateStore.Save(productSlug, hwInfo.HardwareId, cache);
return new LicenseValidationResult
{
IsValid = true,
Status = "valid_offline",
Message = "License validated via secure offline cache",
HardwareId = hwInfo.HardwareId,
IsCached = true,
ExpiresAt = cache.ExpiresAt
};
}
return new LicenseValidationResult
{
IsValid = false,
Status = "network_error",
Message = $"Server communication error and no valid cache available: {ex.Message}",
HardwareId = hwInfo.HardwareId
};
}
return new LicenseValidationResult
{
IsValid = false,
Status = "unknown_error",
Message = "Validation failed.",
HardwareId = hwInfo.HardwareId
};
}
public async Task<bool> DeactivateAsync(string productSlug, string licenseKey, string serverBaseUrl, string authToken = "")
{
var hwInfo = HardwareId.GetHardwareId(productSlug);
var payload = new
{
product = productSlug,
license_key = licenseKey,
hardware_id = hwInfo.HardwareId,
nonce = Guid.NewGuid().ToString("N")
};
string jsonStr = JsonSerializer.Serialize(payload);
var content = new StringContent(jsonStr, Encoding.UTF8, "application/json");
string endpoint = $"{serverBaseUrl.TrimEnd('/')}/api/license/v1/deactivate";
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
Content = content
};
if (!string.IsNullOrWhiteSpace(authToken))
{
request.Headers.Add("X-Watchdog-Key", authToken);
request.Headers.Add("Authorization", $"Bearer {authToken}");
}
try
{
HttpResponseMessage response = await _httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
}
catch
{
return false;
}
}
}