--- phase: 07-license-core plan: 01 subsystem: licensing tags: [rsa, cryptography, wmi, fingerprint, unit-tests, net472] requires: - phase: 06-rename-cleanup provides: NcProgramManager namespace + renamed csproj files provides: - IMachineFingerprint interface + MachineFingerprint WMI implementation - ILicenseValidator interface + LicenseValidator RSA-SHA256 verifier - LicenseFile.TryRead helper - 6 unit tests covering valid/invalid/tampered/missing license scenarios affects: - 08-license-integration (wires these into Program.cs) - 09-license-generator (produces real key pair; PublicKeyXml PLACEHOLDER replaced here) tech-stack: added: [] patterns: - Constructor injection for testability (internal ctor accepts public key XML) - RSACryptoServiceProvider + SHA256CryptoServiceProvider for .NET 4.7.2 compat key-files: created: - Licensing/IMachineFingerprint.cs - Licensing/ILicenseValidator.cs - Licensing/LicenseFile.cs - Licensing/MachineFingerprint.cs - Licensing/LicenseValidator.cs - NcProgramManager.Tests/Unit/LicenseValidatorTests.cs modified: - NcProgramManager.csproj - NcProgramManager.Tests/NcProgramManager.Tests.csproj key-decisions: - "RSA asymmetric: private key vendor-only, public key embedded in binary" - "PublicKeyXml is PLACEHOLDER until Phase 09 generates real key pair" - "Constructor injection: internal LicenseValidator(string publicKeyXml) for test isolation" - "SHA256CryptoServiceProvider (not HashAlgorithmName) — .NET 4.7.2 RSACryptoServiceProvider overload" patterns-established: - "LicenseValidator(string publicKeyXml) internal ctor mirrors NcProgramValidator injection pattern" - "TryRead out-param bool pattern: no exceptions escape from file I/O" duration: ~2h (split across sessions) started: 2026-05-17T00:00:00Z completed: 2026-05-17T21:00:00Z --- # Phase 07 Plan 01: license-core Summary **RSA offline license core shipped: IMachineFingerprint + ILicenseValidator interfaces, WMI fingerprint reader, RSA-SHA256 verifier with injectable key, LicenseFile TryRead helper — 118 tests pass (116 run, 2 Fanuc FOCAS ignored).** ## Performance | Metric | Value | |--------|-------| | Duration | ~2h | | Started | 2026-05-17 | | Completed | 2026-05-17 | | Tasks | 3 completed | | Files modified | 8 | ## Acceptance Criteria Results | Criterion | Status | Notes | |-----------|--------|-------| | AC-1: Machine fingerprint readable via interface | Pass | MachineFingerprint : IMachineFingerprint — WMI logic extracted (not testable on Linux; verified via code review) | | AC-2: Valid license passes validation | Pass | Validate_ValidLicense_ReturnsTrue — in-memory RSA sign+verify | | AC-3: Invalid license rejected | Pass | Validate_WrongMachineFingerprint_ReturnsFalse, Validate_TamperedLicense_ReturnsFalse | | AC-4: Corrupted/missing license file handled | Pass | LicenseFile_TryRead_NonExistentPath_ReturnsFalse | ## Accomplishments - 5 new files in `Licensing/` — interfaces, implementations, file helper — clean DI-ready layer - 6 LicenseValidator tests all pass in Docker (Mono 6.12 / .NET 4.7.2) - PublicKeyXml PLACEHOLDER documented — Phase 09 replaces with real key pair - Full test suite: 118 total, 116 pass, 2 ignored (Fanuc FOCAS, Windows-only DLL, expected) ## Files Created/Modified | File | Change | Purpose | |------|--------|---------| | `Licensing/IMachineFingerprint.cs` | Created | Interface for fingerprint provider | | `Licensing/ILicenseValidator.cs` | Created | Interface for RSA validator | | `Licensing/LicenseFile.cs` | Created | TryRead: reads .lic base64, returns false on missing/empty | | `Licensing/MachineFingerprint.cs` | Created | WMI: CPU ProcessorID + BaseBoard SerialNumber | | `Licensing/LicenseValidator.cs` | Created | RSACryptoServiceProvider VerifyData with SHA256 | | `NcProgramManager.Tests/Unit/LicenseValidatorTests.cs` | Created | 6 NUnit tests; generates ephemeral key pair in SetUp | | `NcProgramManager.csproj` | Modified | +5 Compile entries for Licensing/*.cs | | `NcProgramManager.Tests/NcProgramManager.Tests.csproj` | Modified | +1 Compile entry for LicenseValidatorTests.cs | ## Decisions Made | Decision | Rationale | Impact | |----------|-----------|--------| | RSA asymmetric (not AES) | Private key vendor-only; embedded public key safe to decompile | Phase 09 must generate real 2048-bit key pair | | `RSACryptoServiceProvider` + `SHA256CryptoServiceProvider` | .NET 4.7.2: `VerifyData(byte[], object, byte[])` overload — `HashAlgorithmName` variant requires RSA base class | Locked to this API; no upgrade path issue | | PublicKeyXml = PLACEHOLDER | Real key not yet generated; prevents Phase 07 from blocking on Phase 09 | Phase 09 outputs real XML; copy-paste into LicenseValidator.cs | | Internal constructor injection | Testable without Windows WMI DLL | Same pattern as NcProgramValidator — consistent across codebase | ## Deviations from Plan ### Summary | Type | Count | Impact | |------|-------|--------| | Auto-fixed | 1 | Pre-existing bug, zero scope creep | | Scope additions | 0 | — | | Deferred | 0 | — | ### Auto-fixed Issues **1. Build — Missing `using System;` in CncMachineFactoryTests.cs** - **Found during:** Docker build (msbuild step) - **Issue:** `InvalidOperationException` unresolved — `using System;` absent from `CncMachineFactoryTests.cs` - **Fix:** Added `using System;` at top of file - **Files:** `NcProgramManager.Tests/Unit/CncMachineFactoryTests.cs` - **Verification:** Docker build passed; all 118 tests ran **2. Dockerfile.test — Stale FanucProgramManager paths** - **Found during:** First Docker build attempt - **Issue:** Dockerfile.test still referenced `FanucProgramManager.*` paths from before Phase 06 rename - **Fix:** Updated all paths to `NcProgramManager.*` - **Files:** `Dockerfile.test` - **Verification:** Docker build and test run succeeded ## Next Phase Readiness **Ready:** - `IMachineFingerprint` / `ILicenseValidator` injectable into any caller - `LicenseValidator` verified correct with real RSA operations - `LicenseFile.TryRead` ready for `-licfile=` arg wiring **Concerns:** - PublicKeyXml is PLACEHOLDER — production build will silently reject all licenses until Phase 09 provides real key and it's copied in - `MachineFingerprint` uses WMI — only tested via code review on Linux; validate on Windows hardware **Blockers:** - None — Phase 08 can proceed --- *Phase: 07-license-core, Plan: 01* *Completed: 2026-05-17*