--- phase: 09-license-generator plan: 01 type: execute wave: 1 depends_on: ["07-01"] files_modified: - ~/Documents/mine/c#/LicenseGenerator/LicenseGenerator.csproj - ~/Documents/mine/c#/LicenseGenerator/LicenseGenerator.sln - ~/Documents/mine/c#/LicenseGenerator/Program.cs - ~/Documents/mine/c#/LicenseGenerator/InputArgs.cs - ~/Documents/mine/c#/LicenseGenerator/Commands/KeygenCommand.cs - ~/Documents/mine/c#/LicenseGenerator/Commands/FingerprintCommand.cs - ~/Documents/mine/c#/LicenseGenerator/Commands/SignCommand.cs - ~/Documents/mine/c#/LicenseGenerator/Properties/AssemblyInfo.cs - Licensing/LicenseValidator.cs autonomous: false --- ## Goal Build a standalone vendor console tool (`LicenseGenerator.exe`) in a separate repository at `~/Documents/mine/c#/LicenseGenerator`. Three commands: `keygen` (RSA pair), `fingerprint` (WMI), `sign` (produce `.lic`). After running keygen, replace the PLACEHOLDER in `LicenseValidator.cs` with the real public key. ## Purpose Completes the offline license system. Without a real key pair, all production licenses are rejected. This tool gives the vendor the ability to issue machine-bound licenses and embeds the matching public key into the product binary. ## Output - `~/Documents/mine/c#/LicenseGenerator/` — new standalone .NET 4.7.2 console project and solution - Git repo initialized, remote: `ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git` - `Licensing/LicenseValidator.cs` (in NcProgramManager) — PLACEHOLDER replaced with real RSA 2048-bit public key XML ## Project Context @.paul/PROJECT.md @.paul/STATE.md ## Prior Work @.paul/phases/07-license-core/07-01-SUMMARY.md ## Source Files @Licensing/LicenseValidator.cs @Licensing/MachineFingerprint.cs @Licensing/LicenseFile.cs ## AC-1: keygen produces RSA 2048-bit key pair ```gherkin Given LicenseGenerator.exe is present When vendor runs `LicenseGenerator.exe keygen` Then private.key.xml written to current dir, public key XML printed to stdout And stdout message instructs vendor to paste public key into LicenseValidator.cs ``` ## AC-2: fingerprint command prints machine fingerprint ```gherkin Given LicenseGenerator.exe running on Windows with WMI access When vendor runs `LicenseGenerator.exe fingerprint` Then machine fingerprint string printed to stdout (CPU ProcessorID + BaseBoard SerialNumber, same logic as MachineFingerprint.GetFingerprint()) ``` ## AC-3: sign command produces valid .lic file ```gherkin Given private.key.xml exists and fingerprint string is known When vendor runs `LicenseGenerator.exe sign -key=private.key.xml -fingerprint= -out=license.lic` Then license.lic written containing base64 RSA-SHA256 signature And LicenseValidator(publicKeyXml).Validate(fp, File.ReadAllText("license.lic").Trim()) returns true ``` ## AC-4: LicenseValidator.cs uses real public key ```gherkin Given keygen was run and public key XML captured When PublicKeyXml constant in LicenseValidator.cs is updated from PLACEHOLDER to real XML Then a license signed by the matching private key passes Validate() And a license signed by any other key fails Validate() ``` Task 1: Create LicenseGenerator project and git repo ~/Documents/mine/c#/LicenseGenerator/LicenseGenerator.sln, ~/Documents/mine/c#/LicenseGenerator/LicenseGenerator.csproj, ~/Documents/mine/c#/LicenseGenerator/Program.cs, ~/Documents/mine/c#/LicenseGenerator/InputArgs.cs, ~/Documents/mine/c#/LicenseGenerator/Properties/AssemblyInfo.cs, ~/Documents/mine/c#/LicenseGenerator/.gitignore Create `~/Documents/mine/c#/LicenseGenerator/` as a fully standalone project (NOT inside NcProgramManager). **Directory layout:** ``` LicenseGenerator/ LicenseGenerator.sln LicenseGenerator.csproj Program.cs InputArgs.cs Commands/ (empty dir, populated in Task 2+3) Properties/ AssemblyInfo.cs .gitignore ``` **LicenseGenerator.csproj:** - OutputType: Exe, TargetFrameworkVersion: v4.7.2, Platform: AnyCPU - References: System, System.Core, System.Management - Compile includes: Program.cs, InputArgs.cs, Properties/AssemblyInfo.cs, Commands\KeygenCommand.cs, Commands\FingerprintCommand.cs, Commands\SignCommand.cs **LicenseGenerator.sln:** minimal VS2019-compatible solution referencing LicenseGenerator.csproj **InputArgs.cs** (namespace LicenseGenerator): - Field: `string Command` — args[0] if present, else "" - Field: `string KeyPath` — value of `-key=` - Field: `string Fingerprint` — value of `-fingerprint=` - Field: `string OutPath` — value of `-out=`, default "license.lic" - Static factory: `InputArgs Parse(string[] args)` — split each arg on first '=' **Program.cs** (namespace LicenseGenerator): - Call `InputArgs.Parse(args)` - Switch Command: "keygen" → KeygenCommand.Execute(), "fingerprint" → FingerprintCommand.Execute(), "sign" → SignCommand.Execute(args), default → print usage + Environment.Exit(1) - Usage text lists all three commands **AssemblyInfo.cs:** standard .NET 4.7.2 template, AssemblyVersion "1.0.0.0", product "LicenseGenerator" **.gitignore:** standard C# gitignore (bin/, obj/, *.user, .vs/, packages/) **Git setup (run after creating files):** ```bash cd ~/Documents/mine/c#/LicenseGenerator git init git remote add origin ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git ``` Avoid: any reference to NcProgramManager.csproj or NcProgramManager.sln `ls ~/Documents/mine/c#/LicenseGenerator/` shows .sln, .csproj, Program.cs, InputArgs.cs, Properties/, Commands/, .gitignore `git -C ~/Documents/mine/c#/LicenseGenerator remote -v` shows origin pointing to ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git Project structure created; git repo initialized with correct remote. Task 2: Implement keygen and fingerprint commands ~/Documents/mine/c#/LicenseGenerator/Commands/KeygenCommand.cs, ~/Documents/mine/c#/LicenseGenerator/Commands/FingerprintCommand.cs **KeygenCommand.cs** (namespace LicenseGenerator.Commands): ```csharp internal static class KeygenCommand { internal static void Execute() { using (var rsa = new RSACryptoServiceProvider(2048)) { string privateXml = rsa.ToXmlString(true); string publicXml = rsa.ToXmlString(false); File.WriteAllText("private.key.xml", privateXml); Console.WriteLine("=== PUBLIC KEY XML — paste into LicenseValidator.cs ==="); Console.WriteLine(publicXml); Console.WriteLine("========================================================"); Console.WriteLine("Private key saved: private.key.xml"); Console.WriteLine("KEEP private.key.xml SECRET — never distribute it."); } } } ``` Usings: System, System.IO, System.Security.Cryptography **FingerprintCommand.cs** (namespace LicenseGenerator.Commands): - Duplicate WMI logic from MachineFingerprint.GetFingerprint(): 1. ManagementClass("win32_processor") → processorID property 2. ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_BaseBoard") → SerialNumber 3. Concatenate → print to Console.WriteLine - Wrap in try/catch: on exception print "Error reading fingerprint (Windows + WMI required): " + ex.Message, Environment.Exit(1) - Usings: System, System.Management Avoid: referencing MachineFingerprint class from NcProgramManager — keep standalone On Linux/Mono: `LicenseGenerator.exe keygen` writes private.key.xml and prints RSAKeyValue XML to stdout. On Windows: `LicenseGenerator.exe fingerprint` prints non-empty CPU+board string. AC-1 satisfied: keygen generates 2048-bit RSA pair. AC-2 satisfied: fingerprint reads WMI. Task 3: Implement sign command ~/Documents/mine/c#/LicenseGenerator/Commands/SignCommand.cs **SignCommand.cs** (namespace LicenseGenerator.Commands): ```csharp internal static class SignCommand { internal static void Execute(InputArgs args) { if (string.IsNullOrEmpty(args.KeyPath)) { Console.Error.WriteLine("Missing: -key="); Environment.Exit(1); } if (string.IsNullOrEmpty(args.Fingerprint)) { Console.Error.WriteLine("Missing: -fingerprint="); Environment.Exit(1); } string privateXml = File.ReadAllText(args.KeyPath); byte[] data = Encoding.UTF8.GetBytes(args.Fingerprint); using (var rsa = new RSACryptoServiceProvider()) { rsa.FromXmlString(privateXml); byte[] signature = rsa.SignData(data, new SHA256CryptoServiceProvider()); string base64 = Convert.ToBase64String(signature); File.WriteAllText(args.OutPath, base64); Console.WriteLine("License written: " + args.OutPath); } } } ``` Usings: System, System.IO, System.Security.Cryptography, System.Text SHA256CryptoServiceProvider (not HashAlgorithmName) — .NET 4.7.2 RSACryptoServiceProvider constraint. SignData here MUST pair with VerifyData in LicenseValidator — same provider type on both sides. Avoid: HashAlgorithmName overloads — not available in .NET 4.7.2 RSACryptoServiceProvider `LicenseGenerator.exe sign -key=private.key.xml -fingerprint=TEST_FP -out=test.lic` → test.lic contains non-empty base64. Cross-check: `new LicenseValidator(publicKeyXml).Validate("TEST_FP", File.ReadAllText("test.lic").Trim())` returns true. AC-3 satisfied: sign command produces .lic file verifiable by LicenseValidator. LicenseGenerator project complete — keygen, fingerprint, sign commands implemented. 1. Build: `msbuild ~/Documents/mine/c#/LicenseGenerator/LicenseGenerator.sln /p:Configuration=Release` (or on Windows: build via VS or msbuild) 2. Navigate to output: `LicenseGenerator/bin/Release/` 3. Run: `LicenseGenerator.exe keygen` 4. Capture the PUBLIC KEY XML printed between the `===` banners 5. Paste the captured XML in the resume signal below Note: keygen works on Linux (Mono) and Windows — no WMI needed for key generation. Paste captured public key XML (the full RSAKeyValue string) to continue Task 4: Replace PublicKeyXml PLACEHOLDER in LicenseValidator.cs Licensing/LicenseValidator.cs In NcProgramManager project, replace the PLACEHOLDER in `Licensing/LicenseValidator.cs`. Current (lines 11-15): ```csharp private const string PublicKeyXml = "" + "PLACEHOLDER" + "AQAB" + ""; ``` Replace with the real public key XML from keygen output as a single string literal: ```csharp // Real vendor public key — generated YYYY-MM-DD with LicenseGenerator keygen. private const string PublicKeyXml = "[full RSAKeyValue XML from keygen]"; ``` Avoid: embedding private key — public key only (keygen ToXmlString(false) output) Avoid: multi-line concatenation — single string literal is fine for RSA XML `msbuild NcProgramManager.csproj` — no errors. All 116 existing tests pass (unit tests use ephemeral keys — unaffected). grep "PLACEHOLDER" Licensing/LicenseValidator.cs → no match. AC-4 satisfied: LicenseValidator.cs uses real 2048-bit public key; production licenses now verifiable. Task 5: Initial commit and push LicenseGenerator repo ~/Documents/mine/c#/LicenseGenerator/ (all files) ```bash cd ~/Documents/mine/c#/LicenseGenerator git add LicenseGenerator.sln LicenseGenerator.csproj Program.cs InputArgs.cs \ Commands/KeygenCommand.cs Commands/FingerprintCommand.cs Commands/SignCommand.cs \ Properties/AssemblyInfo.cs .gitignore git commit -m "feat: initial LicenseGenerator — keygen, fingerprint, sign commands" git push -u origin main ``` Do NOT commit private.key.xml or any generated .lic files. Verify .gitignore excludes bin/, obj/, *.user, .vs/. `git -C ~/Documents/mine/c#/LicenseGenerator log --oneline` shows initial commit. LicenseGenerator source pushed to ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git ## DO NOT CHANGE - `Licensing/MachineFingerprint.cs` — WMI logic duplicated in FingerprintCommand; no changes to original - `Licensing/LicenseValidator.cs` — touch only the `PublicKeyXml` constant (Task 4 only) - `Program.cs` (NcProgramManager) — license integration complete; no changes - `NcProgramManager.Tests/` — no test changes in this plan - `NcProgramManager.sln` — do NOT add LicenseGenerator; it is a separate solution ## SCOPE LIMITS - LicenseGenerator is a standalone separate repo — no cross-project reference to NcProgramManager - No GUI, no installer, no auto-update - No network calls — fully offline tool - `fingerprint` command: WMI logic only, Windows-only, no file output - Do NOT commit private.key.xml to git Before declaring plan complete: - [ ] `~/Documents/mine/c#/LicenseGenerator/` exists as independent git repo - [ ] `git remote -v` shows ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git - [ ] `LicenseGenerator.exe keygen` writes private.key.xml + prints RSAKeyValue XML - [ ] `LicenseGenerator.exe sign -key=private.key.xml -fingerprint=TEST -out=test.lic` writes base64 - [ ] `LicenseValidator.cs` PublicKeyXml has no "PLACEHOLDER" - [ ] `msbuild NcProgramManager.csproj` passes, all 116 tests pass - [ ] LicenseGenerator source committed and pushed to remote - LicenseGenerator builds as standalone .NET 4.7.2 console EXE in its own repo - All three commands (keygen, fingerprint, sign) implemented and functional - LicenseValidator.cs uses real RSA 2048-bit public key - Existing NcProgramManager test suite unaffected (116 pass, 2 ignored) - Source pushed to ssh://git@3nt-git.duckdns.org:222/davide.trentin/license-generator.git After completion, create `.paul/phases/09-license-generator/09-01-SUMMARY.md`