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 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 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; } } }