207 lines
7.4 KiB
C#
207 lines
7.4 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|