sqlinsight-pro/tools/SQLInsight.LicenseTool/Program.cs
Claude 1ba274d699
Some checks are pending
CI / Build og unit tests (push) Waiting to run
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Blocked by required conditions
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Blocked by required conditions
Fase 3: offline-verifiable signed licenses and license tool
- SQLInsight.Licensing: ECDSA P-256 signed documents (SQLI1.payload.sig),
  licenses with plan, expiry, instance count and key id for rotation;
  30-day grace; invalid or missing licenses are states, never errors,
  and never stop monitoring (a license only adds hub sync and support)
- Trusted public keys are embedded; the source ships with none, so a
  published key can never mint licenses. The vendor adds its own.
- tools/SQLInsight.LicenseTool: keygen, issue, verify
- *.private.pem ignored by git

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E3EExwndy8Z22EK2Qpt6BK
2026-09-29 08:30:28 +00:00

72 lines
2.7 KiB
C#

// Vendor tool: generate a signing key pair, issue licenses, verify them. Runs on the vendor's machine only.
using System.Globalization;
using System.Security.Cryptography;
using SQLInsight.Licensing;
return args.FirstOrDefault() switch
{
"keygen" => KeyGen(Option("--out") ?? "."),
"issue" => Issue(),
"verify" => Verify(),
_ => Usage(),
};
int Usage()
{
Console.Error.WriteLine("""
SQLInsight.LicenseTool
keygen --out <mappe> Opret nøglepar (privat PEM + offentlig nøgle)
issue --key <privat.pem> --kid <id> --customer <navn> --plan <Professional|Enterprise>
--expires <yyyy-mm-dd> [--instances <antal>] Udsted licens
verify --public <base64> --kid <id> <licens> Verificér licens
""");
return 1;
}
int KeyGen(string folder)
{
using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var kid = "k" + DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
Directory.CreateDirectory(folder);
var privatePath = Path.Combine(folder, $"sqlinsight-{kid}.private.pem");
File.WriteAllText(privatePath, key.ExportPkcs8PrivateKeyPem());
var publicKey = Convert.ToBase64String(key.ExportSubjectPublicKeyInfo());
Console.WriteLine($"Privat nøgle: {privatePath} (opbevar sikkert, aldrig i git)");
Console.WriteLine("Tilføj til src/SQLInsight.Licensing/trusted-keys.json:");
Console.WriteLine($$"""{ "id": "{{kid}}", "publicKey": "{{publicKey}}" }""");
return 0;
}
int Issue()
{
using var key = ECDsa.Create();
key.ImportFromPem(File.ReadAllText(Required("--key")));
var license = new License
{
LicenseId = Guid.NewGuid().ToString("N"),
Customer = Required("--customer"),
Plan = Enum.Parse<LicensePlan>(Required("--plan"), ignoreCase: true),
IssuedAt = DateTimeOffset.UtcNow,
ExpiresAt = DateTimeOffset.Parse(Required("--expires") + "T23:59:59Z", CultureInfo.InvariantCulture),
MaxInstances = int.Parse(Option("--instances") ?? "0", CultureInfo.InvariantCulture),
KeyId = Required("--kid"),
};
Console.WriteLine(SignedDocument.Sign(license, key));
return 0;
}
int Verify()
{
var keys = new TrustedKeys([KeyValuePair.Create(Required("--kid"), Required("--public"))]);
var status = new LicenseVerifier(keys, TimeProvider.System).Check(args[^1]);
Console.WriteLine($"{status.State}: {status.Message}");
return status.State is LicenseState.Valid ? 0 : 2;
}
string? Option(string name)
{
var i = Array.IndexOf(args, name);
return i >= 0 && i + 1 < args.Length ? args[i + 1] : null;
}
string Required(string name) => Option(name) ?? throw new ArgumentException($"{name} mangler.");