// 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 Opret nøglepar (privat PEM + offentlig nøgle) issue --key --kid --customer --plan --expires [--instances ] Udsted licens verify --public --kid 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(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.");