Fix cert bug with multi-component publisher validation#573
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes publisher mismatch errors during winapp package when the certificate subject uses a multi-component X.500 distinguished name (DN), by extracting and validating the full DN rather than only the CN component.
Changes:
- Normalizes publisher input so full DNs (
CN=...,...) are preserved while bare names are wrapped asCN=.... - Extracts the full certificate subject DN and validates manifest vs certificate publisher via
X500DistinguishedName.RawDatacomparison. - Expands test coverage for multiple DN formats and updates test cert store cleanup subjects.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/winapp-CLI/WinApp.Cli/Services/CertificateService.cs | Normalizes publisher DN handling; returns full cert subject; compares publishers using X.500 raw bytes. |
| src/winapp-CLI/WinApp.Cli.Tests/PackageCommandTests.cs | Adds/updates tests and cleanup subjects for multi-component publisher DNs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Build Metrics ReportBinary Sizes
Test Results✅ 1265 passed, 1 skipped out of 1266 tests in 441.9s (+67 tests, -16.4s vs. baseline) Test Coverage❌ 17.3% line coverage, 36.5% branch coverage · ✅ no change vs. baseline CLI Startup Time33ms median (x64, Updated 2026-06-19 23:24:50 UTC · commit |
…crosoft/winappCli into zt/567-cert-CN-validation-bug
PR Review —
|
| Dimension | Result |
|---|---|
| security | ⚠ 1 finding (folded into M4) |
| correctness | ⚠ 1 finding |
| cli-ux | ⚠ 2 findings |
| alternative-solution | ⚠ 2 findings (folded into M1/M4) |
| test-coverage | ⚠ 2 findings |
| docs-and-samples | ✓ clean |
| packaging | ⚠ 2 findings (folded into M4/L2) |
| multi-model | 0/1 highs confirmed · 1 downgraded to medium · 0 new |
Findings
| ID | File:lines | Domain | Finding |
|---|---|---|---|
| M1 | src/winapp-CLI/WinApp.Cli/Helpers/PublisherDnHelper.cs:41-69 |
correctness, alternative-solution | Escaped-comma DNs (CN=Last\, First) rejected by the BCL ctor → double-prefixed to CN=CN=… (was High; cross-model downgraded) |
| M2 | src/winapp-CLI/WinApp.Cli/Helpers/PublisherDnHelper.cs:77-128 |
test-coverage | DN edge cases untested: escaped comma, + multi-valued RDN, lowercase attrs, RDN ordering, null input |
| M3 | src/winapp-CLI/WinApp.Cli/Services/CertificateService.cs:449-450 |
cli-ux | Mismatch "fix" hint uses manifestPath.Name only — fails when run outside the manifest folder |
| M4 | src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs:118-124 |
security, packaging, alternative-solution | {PublisherName} injected into the manifest without XML escaping (while {PublisherDN} is escaped) |
| M5 | src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs:233-260 |
test-coverage | No end-to-end test that a generated manifest's Publisher matches a generated cert's subject for complex DNs |
| L1 | docs/usage.md:644-645 |
cli-ux | --publisher still documented as a plain "name"; doesn't mention DN support / bare-name wrapping |
| L2 | src/winapp-npm/src/winapp-commands.ts:88-89 |
packaging | Generated npm wrapper doc-comment stale vs the updated CLI schema description |
Details
M1 — src/winapp-CLI/WinApp.Cli/Helpers/PublisherDnHelper.cs:41-69 · correctness + alternative-solution · Multi-model: downgraded from High
- Severity: medium · Confidence: high
- Finding: RFC-style backslash-escaped value delimiters in a DN (e.g.
CN=Last\, First) are misclassified and get an extraCN=prefix, producingCN=CN=Last\, First. - Evidence:
IsDistinguishedNamedelegates tonew X500DistinguishedName(input)(lines 24-27) — so quoted values, lowercase attribute types, and+multi-valued RDNs already parse correctly. But that constructor rejects backslash-escaped delimiters, soNormalizefalls through toreturn $"CN={trimmed}"(lines 64-69). Downstream,GenerateDevCertificateAsyncpasses the malformed subject intonew CertificateRequest(...)(CertificateService.cs:42-63), which throws; the validation gate reparses both sides and comparesRawData(CertificateService.cs:443-446). Net effect: false rejection / cert-generation failure — not a trust bypass (cross-model confirmed no false-match path). - Recommendation: For the bare-name path, build the DN with
X500DistinguishedNameBuilder.AddCommonName(trimmed)(available on net10.0-windows) instead of string interpolation, so the CN value is escaped correctly and escaped-comma inputs round-trip. Add the escaped-comma case to tests (see M2).
M4 — src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs:118-124 · security + packaging + alternative-solution
- Severity: medium · Confidence: high
- Finding: The
PublisherDisplayNamevalue is written into the manifest via raw string.Replacewithout XML escaping, while the sibling{PublisherDN}replacement is escaped — so a publisher containing&,<,>, or"produces an invalid (or injected) manifest. - Evidence:
.Replace("{PublisherDN}", PublisherDnHelper.XmlEscape(publisherDN))(121) vs.Replace("{PublisherName}", publisherName)(122); template uses<PublisherDisplayName>{PublisherName}</PublisherDisplayName>.AppxManifestDocument.cs:179-182already exposes a typedIdentityPublisher. - Recommendation: Preferred fix addresses all three domains at once: parse the template with
AppxManifestDocument, setIdentityPublisher/IdentityName/IdentityVersionvia typed properties, and serialize withToXml()soXDocumenthandles escaping — instead of chained.Replace+ manualXmlEscape. At minimum, alsoXmlEscape(publisherName). Add a manifest-generation test withCN=A&B.
M2 — src/winapp-CLI/WinApp.Cli/Helpers/PublisherDnHelper.cs:77-128 · test-coverage
- Severity: medium · Confidence: high
- Finding: DN display/component parsing lacks coverage for several edge cases the fix is meant to handle.
- Evidence: Tests cover simple CN, multi-RDN, non-CN, quoted comma, and empty/whitespace, but not escaped comma
\,,+multi-valued RDNs, lowercase attribute types, RDN-ordering differences, separator-whitespace equivalence, or null input (PublisherDnHelperTests.cs:14-24, 33-40, 72-78). - Recommendation: Add
[DataRow]cases for those DN forms acrossIsDistinguishedName,Normalize, andGetDisplayName— the escaped-comma row would directly catch M1.
M5 — src/winapp-CLI/WinApp.Cli/Services/ManifestTemplateService.cs:233-260 · test-coverage
- Severity: medium · Confidence: high
- Finding: No end-to-end test verifies that a generated manifest's Publisher matches a generated certificate's subject for complex DNs — the exact scenario this PR fixes.
- Evidence: Certificate tests manually write/replace the manifest publisher before validation (
PackageCommandTests.cs:755-760, 809-817); manifest-generation tests assert only simpleCN=TestPublisher(ManifestCommandTests.cs:69-103). - Recommendation: Add an integration test that generates a manifest with
CN+O+Cand a non-CN publisher, generates a cert from the same publisher, then asserts the manifestIdentity/Publisherequals the certSubjectName(RawData) — a true regression test for the reported bug.
M3 — src/winapp-CLI/WinApp.Cli/Services/CertificateService.cs:449-450 · cli-ux
- Severity: medium · Confidence: high
- Finding: The publisher-mismatch error suggests a fix command that drops the manifest's directory, so the suggested command can fail when the user isn't in the manifest folder.
- Evidence:
Regenerate the certificate with 'winapp cert generate --manifest "{manifestPath.Name}"'(line 450) uses.Name(filename only). - Recommendation: Use the resolved/cwd-relative manifest path in the suggested
--manifestvalue so the copy-pasted command works as-is.
L1 — docs/usage.md:644-645 · cli-ux
- Severity: low · Confidence: high
- Finding: Usage docs still describe
--publisheras a generic name, omitting the new DN support and bare-name wrapping behavior. - Evidence:
- `--publisher <name>` - Publisher name for certificate(line 645). - Recommendation: Match the updated
cert generateschema wording (DN support); fix other stale--publisherentries too.
L2 — src/winapp-npm/src/winapp-commands.ts:88-89 · packaging
- Severity: low · Confidence: high
- Finding: The autogenerated npm wrapper doc-comment wasn't regenerated after the CLI schema description changed.
- Evidence:
/** Publisher name for the generated certificate. If not specified, will be inferred from manifest. */vs the schema's new "Publisher distinguished name (DN)…". - Recommendation: Run
npm run generate-commandsand include the regeneratedwinapp-commands.ts.
Clean dimension (what was verified)
- docs-and-samples: the hand-written
docs/fragments/skills/winapp-cli/signing.mdand its autogenerated mirrors (.github/plugin+.claudeSKILL.md,winapp.agent.md) are consistent;docs/cli-schema.jsonwas regenerated and matches the command descriptions; no stale guidance describing the old "must start with CN=" behavior remains.
Generated by the winappcli pr-review skill · 8 specialist passes + GPT-5.4 cross-model verification · 2026-06-19. No code was built or executed; verify findings before acting. 🤖
nmetulev
left a comment
There was a problem hiding this comment.
Approving, contingent on reviewing and addressing any relevant feedback from the ai generated review
fixes #567
Users with multi-component publisher DNs got a false mismatch error during winapp package. The certificate was generated correctly, but ExtractPublisherFromCertificate only extracted the CN value via regex CN=([^,]+), dropping L/S/C/O fields.
Fix
Tests
Added 15 parameterized tests covering simple CN, GUID-style, locale fields, full corporate DN, org-only, country-only, spaces in values, bare name normalization, and whitespace trimming. Fixed test cert store cleanup to include all new subjects.