---
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*
