// .NET 8 / Windows / ASP.NET. No external NuGet dependencies. using System; using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading.Tasks; public sealed class LicenseClient { readonly HttpClient http = new() { Timeout = TimeSpan.FromSeconds(15) }; readonly string baseUrl, pem, product, licenseKey, account, device; public LicenseClient(string baseUrl, string pinnedPublicPem, string product, string licenseKey, string accountId, string deviceId) { this.baseUrl=baseUrl.TrimEnd('/');pem=pinnedPublicPem;this.product=product;this.licenseKey=licenseKey;account=accountId;device=deviceId; } public async Task RequestAsync(string action="verify") { if (action is not ("check" or "activate" or "verify" or "heartbeat" or "deactivate" or "bootstrap")) throw new ArgumentException("Unknown action"); string nonce=Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLowerInvariant(); using var response=await http.PostAsJsonAsync(baseUrl+"/api/license/"+action,new {product,license_key=licenseKey,account_id=account,device_id=device,nonce,platform="windows"}); using var outer=JsonDocument.Parse(await response.Content.ReadAsStringAsync()); byte[] bytes=Convert.FromBase64String(outer.RootElement.GetProperty("signed_payload").GetString()!); using var rsa=RSA.Create();rsa.ImportFromPem(pem); if(!rsa.VerifyData(bytes,Convert.FromBase64String(outer.RootElement.GetProperty("signature").GetString()!),HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1))throw new Exception("Invalid RSA signature"); using var payload=JsonDocument.Parse(bytes);var p=payload.RootElement; if(p.GetProperty("action").GetString()!=action || p.GetProperty("nonce").GetString()!=nonce || p.GetProperty("product").GetString()!=product || p.GetProperty("account_id").GetString()!=account || p.GetProperty("device_id").GetString()!=device)throw new Exception("Request context mismatch"); if(!p.GetProperty("ok").GetBoolean())throw new Exception(p.GetProperty("message").GetString()); long now=DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if(Math.Abs(now-p.GetProperty("server_time").GetInt64())>300 || now>=p.GetProperty("valid_until").GetInt64())throw new Exception("Expired response or incorrect clock"); string hash=Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(licenseKey.Trim().ToUpperInvariant()))).ToLowerInvariant(); if(p.GetProperty("license_key_hash").GetString()!=hash)throw new Exception("Wrong license"); return p.Clone(); } }