Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
cc5352f
Phase 4: implementation plan (PQ & composite + provider polish)
pdb0102 Jun 20, 2026
ae9a58a
Phase 4: validate IScepCrypto seam is additive-only across all 3 PQ t…
pdb0102 Jun 20, 2026
2cbc04c
Phase 4: add ML-DSA/SLH-DSA/ML-KEM entries to the algorithm registry
pdb0102 Jun 20, 2026
32eb7b2
Phase 4: KeySpec.Parse accepts ml-dsa/slh-dsa/ml-kem parameter sets
pdb0102 Jun 20, 2026
2645535
Phase 4: advertise PQ algorithms and tiers (A/B yes, C no) via Crypto…
pdb0102 Jun 20, 2026
7ec1fb4
Phase 4: tier A — ML-DSA/SLH-DSA key generation + PKCS#8 round-trip
pdb0102 Jun 20, 2026
46bf49b
Phase 4: tier A — encode PQ CSR with PQ SubjectPublicKeyInfo and sign…
pdb0102 Jun 20, 2026
88407e9
Phase 4: tier B — catalyst alt-key via subjectAltPublicKeyInfo + --al…
pdb0102 Jun 20, 2026
1b338b4
Phase 4: capabilities drive opinion (CuttingEdge) and PQ servers-suggest
pdb0102 Jun 20, 2026
5518ffc
Phase 4: empirical PQ probe (ML-DSA enroll -> PASSED/FINDING/FAILED)
pdb0102 Jun 20, 2026
8f5447b
Phase 4: crypto info / crypto list CLI
pdb0102 Jun 20, 2026
1cba682
Phase 4: --crypto-provider flag + config set, threaded to all crypto …
pdb0102 Jun 20, 2026
c717353
Phase 4: harden external-provider ALC loading (ambiguity check + shar…
pdb0102 Jun 20, 2026
e5b39ef
Phase 4: review polish — drop unsupported SLH-DSA f-sets from KeySpec…
pdb0102 Jun 20, 2026
b964873
Phase 4: sync plan task statuses to completed
pdb0102 Jun 20, 2026
fe35932
Phase 4: full SLH-DSA SHA2 matrix (s+f) with correct NIST OIDs; sign …
pdb0102 Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# IScepCrypto seam validation vs PQ tiers (Phase 4 pre-flight)

Date: 2026-06-20. Re-validates the Phase-1 on-paper conclusion before writing PQ code.
Reference interface: `src/ScepTestClient.CryptoApi/IScepCrypto.cs` (unchanged by Phase 4).

## Conclusion: additive-only. No IScepCrypto signature changes.

Every PQ tier slots in through the three seams designed in Phase 1 — open OID identifiers +
capability advertisement, opaque `IScepKey` handles, and extensible domain objects. The
`IScepCrypto` interface itself is **not** modified. The only new public surface is additive
(new properties / new registry entries) that existing and external callers ignore.

## Tier A — PQ end-entity key (ML-DSA / SLH-DSA)

- `KeySpec.Parse` gains `ml-dsa:65` / `slh-dsa:128s` (new accepted inputs + a new `Parameter`
property; existing `rsa:<bits>` callers unaffected, `Size` keeps working for RSA).
- `IScepCrypto.GenerateKey(KeySpec, out IScepKey, out string)` — **unchanged signature**; returns an
`IScepKey` whose `AlgorithmOid` is the PQ OID.
- `IScepCrypto.EncodeCsr(Pkcs10, ...)` — **unchanged**; the provider emits a PQ
`SubjectPublicKeyInfo` (`PqcSubjectPublicKeyInfoFactory`) and signs with the PQ signer.
- `ExportPrivateKeyPkcs8` / `ImportPrivateKeyPkcs8` — **unchanged**; PQ via
`PqcPrivateKeyInfoFactory` / `PrivateKeyFactory`.
- BC 2.5.0: **FEASIBLE** (`MLDsaKeyPairGenerator`, `SlhDsaKeyPairGenerator`, `MLDsaParameters`,
`SlhDsaParameters`, the Pqc SPKI/PKCS#8 factories all present).

## Tier B — Catalyst / hybrid alt-key (subjectAltPublicKeyInfo)

- New additive `Pkcs10.AltKey` (`IScepKey?`) property, defaulting null, ignored by existing callers.
- `EncodeCsr` emits the `subjectAltPublicKeyInfo` extension (OID `2.5.29.72`) carrying the alt
key's `SubjectPublicKeyInfo`.
- **No interface change.**
- BC 2.5.0: emitting the alt PUBLIC KEY is **FEASIBLE**; computing a conformant
`altSignatureValue` over the CSR is bleeding-edge and is **NOT** done by the built-in provider —
recorded as a documented limitation (surfaced via this doc + `crypto info`).

## Tier C — PQ transport (ML-KEM EnvelopedData, RFC 9629 KEMRecipientInfo)

- `EncodePkiMessage` already takes the recipient via `PkiMessage.RecipientCaCert` — a PQ recipient
WOULD trigger `KEMRecipientInfo` inside the provider. **No signature change.**
- BC 2.5.0: **NOT FEASIBLE.** The CMS layer exposes only KeyTrans / Kek / KeyAgree / Password
recipient generators; `KemRecipientInfo` exists only as a raw ASN.1 type with no CMS generator,
and the `MLKemKeyPairGenerator` / `*KemGenerator` types are low-level KEM primitives not wired
into `CmsEnvelopedDataGenerator`. The built-in provider therefore advertises
`CryptoCapabilities.PqTiers.TierC = false`; an external provider can implement it through the same
seam without an interface change.

## Composite signatures

- BC 2.5.0 carries only the legacy Dilithium-draft composite OIDs (`id_Dilithium3_ECDSA_P256_SHA256`,
etc.) and the bare `id_composite_key` OID — there is no current ML-DSA composite signing path.
- Scope: composite stays **vocabulary + opinion** (classified `CuttingEdge`); no BC signing
implementation in Phase 4.

## New additive surface introduced by Phase 4

- `KeySpec.Parameter` (string); `KeySpec.Parse` PQ inputs.
- `CryptoCapabilities.PqTiers` (`TierA`/`TierB`/`TierC` bools) + PQ OIDs in
`Signatures` / `AsymmetricKeys` / `Kem`.
- `Pkcs10.AltKey` (`IScepKey?`).
- `Algorithms` registry: ML-DSA-44/65/87, SLH-DSA-128s/192s/256s, ML-KEM-512/768/1024 entries.

None of the above alters an existing method signature; all are new members or new accepted input
values. The seam validated in Phase 1 holds.
2,679 changes: 2,679 additions & 0 deletions docs/superpowers/plans/2026-06-20-scep-test-client-phase-3.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"planPath": "docs/superpowers/plans/2026-06-20-scep-test-client-phase-3.md",
"tasks": [
{
"id": 1,
"subject": "Task 1: FaultDirectives + provider fault branch",
"status": "completed",
"description": "Fill in FaultDirectives (CorruptSignature/SigningTimeSkew/CorruptInnerContent) + single `if (faults != null)` branch in BcPkiMessage.EncodePkiOperation; thread faults from BouncyCastleScepCrypto. Verify: dotnet test --filter FullyQualifiedName~FaultInjection. Files: src/ScepTestClient.CryptoApi/FaultDirectives.cs, src/ScepTestClient.Crypto.BouncyCastle/BcPkiMessage.cs, src/ScepTestClient.Crypto.BouncyCastle/BouncyCastleScepCrypto.cs, tests/ScepTestClient.Tests/FaultInjectionTests.cs"
},
{
"id": 2,
"subject": "Task 2: Builder AllowFaults + client SubmitPkiOperation",
"status": "completed",
"blockedBy": [1],
"description": "ScepRequestBuilder.AllowFaults + Faults property; faults param on SendPkiOperationSync/Async; public SubmitPkiOperation(+Async). Verify: dotnet test --filter FullyQualifiedName~AllowFaults. Files: src/ScepTestClient.Core/ScepRequestBuilder.cs, src/ScepTestClient.Core/ScepClient.cs, tests/ScepTestClient.Tests/AllowFaultsTests.cs"
},
{
"id": 3,
"subject": "Task 3: Extend TestCa/FakeScepServer for fault detection",
"status": "completed",
"blockedBy": [2],
"description": "HandlePkiOperation priority ladder -> failInfo (badMessageCheck/badTime/badAlg/badRequest); PendingMode + BuildPendingCertRep; ExpectedChallenge; VerifyOuterSignature/ReadSigningTime/InnerCsrParses helpers. Verify: dotnet test --filter FullyQualifiedName~FaultMatrixServer. Files: tests/ScepTestClient.Tests/Fakes/TestCa.cs, tests/ScepTestClient.Tests/Fakes/FakeScepServer.cs, tests/ScepTestClient.Tests/FaultMatrixServerTests.cs"
},
{
"id": 4,
"subject": "Task 4: Test result model + ComplianceEngine (full matrix)",
"status": "completed",
"blockedBy": [3],
"description": "CheckOutcome/CheckResult/TestReport/FaultKind/ComplianceCheck/ComplianceEngine; 7-row §10.1 matrix; RunFull classifies PASSED/FAILED/FINDING. Verify: dotnet test --filter FullyQualifiedName~ComplianceEngine. Files under src/ScepTestClient.Core/Testing/ + tests/ScepTestClient.Tests/ComplianceEngineTests.cs"
},
{
"id": 5,
"subject": "Task 5: TestEngine — lifecycle + probe modes",
"status": "completed",
"blockedBy": [4],
"description": "TestEngine.RunLifecycle (GetCACaps->GetCACert->enroll->poll->renew->GetCRL, skip on prereq fail) + RunProbe (beyond-advertised) + RunFull delegate. Verify: dotnet test --filter FullyQualifiedName~TestEngineModes. Files: src/ScepTestClient.Core/Testing/TestEngine.cs, tests/ScepTestClient.Tests/TestEngineModesTests.cs"
},
{
"id": 6,
"subject": "Task 6: Security opinion + thresholds + servers suggest",
"status": "completed",
"description": "OpinionThresholds/SecurityOpinion (postures) + ServerSuggest; ClientConfig.MinRsaKeyBits round-trip. Verify: dotnet test --filter FullyQualifiedName~SecurityOpinion. Files under src/ScepTestClient.Core/Testing/ + Storage/ClientConfig.cs + tests/ScepTestClient.Tests/SecurityOpinionTests.cs"
},
{
"id": 7,
"subject": "Task 7: Jamf timing simulation (--jamf-max-wait)",
"status": "completed",
"blockedBy": [3],
"description": "JamfResult + JamfSimulator.Run: enroll; if PENDING poll until success or max_wait exceeded -> TimedOut. Verify: dotnet test --filter FullyQualifiedName~JamfSimulator. Files: src/ScepTestClient.Core/Testing/JamfResult.cs, JamfSimulator.cs, tests/ScepTestClient.Tests/JamfSimulatorTests.cs"
},
{
"id": 8,
"subject": "Task 8: Report emitters — JUnit + TRX (XML)",
"status": "completed",
"blockedBy": [4],
"description": "JUnitReport + TrxReport via System.Xml.Linq; well-formed; FAILED->failure, FINDING->system-out. Verify: dotnet test --filter FullyQualifiedName~XmlReport. Files: src/ScepTestClient.Core/Reporting/JUnitReport.cs, TrxReport.cs, tests/ScepTestClient.Tests/XmlReportTests.cs"
},
{
"id": 9,
"subject": "Task 9: Report emitters — JSON + Markdown + console summary",
"status": "completed",
"blockedBy": [4],
"description": "JsonReport + MarkdownReport + ConsoleSummary (§12 block). Verify: dotnet test --filter FullyQualifiedName~TextReport. Files: src/ScepTestClient.Core/Reporting/JsonReport.cs, MarkdownReport.cs, ConsoleSummary.cs, tests/ScepTestClient.Tests/TextReportTests.cs"
},
{
"id": 10,
"subject": "Task 10: Scenario / playlist runner",
"status": "completed",
"blockedBy": [4],
"description": "ScenarioFile + ScenarioRunner (Parse + Run): steps run+args+expect aggregated into one TestReport. Verify: dotnet test --filter FullyQualifiedName~ScenarioRunner. Files: src/ScepTestClient.Core/Testing/ScenarioFile.cs, ScenarioRunner.cs, tests/ScepTestClient.Tests/ScenarioRunnerTests.cs"
},
{
"id": 11,
"subject": "Task 11: Challenge sources (Explicit/Simulator/NDES)",
"status": "completed",
"description": "IChallengeSource + Explicit/Simulator/NDES + NdesAdminUrl.Derive; FakeHttpEndpoint test fixture; redact via Redaction.Hash. Verify: dotnet test --filter FullyQualifiedName~ChallengeSource. Files under src/ScepTestClient.Core/Challenge/ + tests/ScepTestClient.Tests/Fakes/FakeHttpEndpoint.cs + ChallengeSourceTests.cs"
},
{
"id": 12,
"subject": "Task 12: CLI — test lifecycle/full/probe + reports + jamf-max-wait",
"status": "completed",
"blockedBy": [5, 7, 8, 9],
"description": "test noun -> engine; ConsoleSummary print; --report-format repeatable -> <root>/runs/; exit by Failed; --jamf-max-wait step. Verify: dotnet test --filter FullyQualifiedName~CliTestCommand. Files: src/ScepTestClient.Cli/CommandRouter.cs, tests/ScepTestClient.Tests/CliTestCommandTests.cs"
},
{
"id": 13,
"subject": "Task 13: CLI — run scenario, servers suggest, challenge-source flags",
"status": "completed",
"blockedBy": [6, 10, 11],
"description": "run <scenario>; servers suggest; --simulator/--ndes* challenge resolution on enroll/get (precedence challenge>simulator>ndes; redacted). Final full-suite green 0 warnings. Verify: dotnet test --filter FullyQualifiedName~CliScenarioSuggest then full dotnet test. Files: src/ScepTestClient.Cli/CommandRouter.cs, tests/ScepTestClient.Tests/CliScenarioSuggestTests.cs"
}
],
"lastUpdated": "2026-06-20"
}
Loading