Skip to content

Initial shell of new algorithm to enable sign-tool signing outside of arcade#16436

Closed
mmitche wants to merge 28 commits intodotnet:mainfrom
mmitche:recursive-signing-rewrite
Closed

Initial shell of new algorithm to enable sign-tool signing outside of arcade#16436
mmitche wants to merge 28 commits intodotnet:mainfrom
mmitche:recursive-signing-rewrite

Conversation

@mmitche
Copy link
Copy Markdown
Member

@mmitche mmitche commented Jan 9, 2026

This is an initial basic implementation signtool in a new library. It involves a few basic components:

  • Signing driver (RecursiveSigning)
  • Certificate identifier
  • File deduplicator
  • Signing graph implementation.
  • Container handling.

The overall algorithm is similar to signtool, but we've abstracted a large portion of functionality into a set of services injected with DI. This allows for MUCH better testability in the long term.

To double check:

… arcade

This is an initial basic implementation signtool in a new library. It involves a few basic components:
- Signing driver (RecursiveSigning)
- Certificate identifier
- File deduplicator
- Signing graph implementation.
- Container handling.

The overall algorithm is similar to signtool, but we've abstracted a large portion of functionality into a set of services injected with DI. This allows for MUCH better testability in the long term.
@mmitche mmitche marked this pull request as draft February 3, 2026 23:50
mmitche and others added 24 commits February 3, 2026 15:53
Implement ESRPCliSigningProvider, a production ISigningProvider that
invokes the ESRP CLI tool (esrpcli.dll) to code-sign files.

Key components:
- ESRPCliSigningProvider: Groups files by certificate and submits all
  groups in parallel using regularSigning mode (-x regularSigning).
  Each cert group gets its own operations JSON and pattern file.
- ESRPCliSigningConfiguration: All ESRP settings including auth mode
  (FederatedToken for PME, Certificate for CORP), service endpoints,
  key vault config, and signing parameters.
- ESRPCliResultParser: Parses ESRP CLI stdout/stderr for success,
  failure, and operation ID extraction.
- IProcessRunner/DefaultProcessRunner: Abstraction for process
  execution enabling testability.

CLI changes:
- Added --esrp, --esrp-id, --esrp-client-id, --esrp-tenant-id,
  --esrp-keyvault-name, --esrp-cert-name, --federated-token,
  --service-connection-id, --verbose, --dry-run flags
- Added signing summary table with timing telemetry
- Replaced FluentAssertions with AwesomeAssertions

Pipeline integration:
- eng/pipelines/templates/steps/recursive-sign.yml step template
- Added recursive signing step to Windows_NT job in eng/build.yml
- signing-config.json with Microsoft400 and NuGet cert definitions

Tests: 97 tests covering result parsing, cert grouping, operations
JSON generation, pattern files, argument construction, dry-run mode,
empty files, and parallel multi-cert signing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implement IFileTypeAnalyzer interface for pluggable file-type analysis.
Add PEFileTypeAnalyzer that detects Authenticode signatures by inspecting
the PE header's CertificateTableDirectory entry (Size > 0 = signed).
Update DefaultFileAnalyzer to dispatch to registered type analyzers.

- IFileTypeAnalyzer: CanAnalyze(fileName) + AnalyzeAsync(stream, fileName)
- PEFileTypeAnalyzer: handles .dll, .exe, .sys, .ocx extensions
- FileTypeInfo model: ExecutableType, IsAlreadySigned, TFM, PublicKeyToken
- DefaultFileAnalyzer: composition with IFileTypeAnalyzer instances
- 10 new tests including synthetic signed PE detection

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Write ESRP CLI stdout/stderr to per-invocation log files (always,
  even in non-verbose mode) for post-mortem diagnostics
- Add LogDirectory config and --log-dir CLI flag
- On failure, surface individual failure details (operation IDs, file
  paths, error JSON) at LogError level so they appear in build logs
- Enhance ESRPCliResultParser to extract 'Failed OperationId:' lines
  from the 'List of failed files:' section
- Pipeline template: publish signing logs as build artifact (always)
- Remove --verbose from pipeline default (logs captured in files instead)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…CommandLine version

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add signRegardless flag to certificate rules for dual-signing scenarios.
Files that are already signed are skipped unless the matching certificate
has signRegardless=true (e.g. for adding a Microsoft signature on top of
a third-party signature).

- ICertificateIdentifier: add SignRegardless property
- ESRPCertificateIdentifier: propagate signRegardless from config
- DefaultCertificateRules: store signRegardless per certificate
- DefaultCertificateRulesReader: parse signRegardless from JSON
- SigningGraph.ComputeInitialState: check SignRegardless before skipping
- RecursiveSigning: log skip/sign-regardless decisions at Info level
- 9 new tests covering skip, sign-regardless, and container scenarios

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the NuGet certificate (CP-401405) from signing-config.json since
the current ESRP identity does not have access to it. DLLs will still be
signed with Microsoft400 (CP-230012). NuGet signing can be re-added once
the proper certificate access is provisioned.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert RecursiveSigning.Cli to a dotnet tool (PackAsTool=true) with
command name 'dotnet-recursive-sign'. Target net10.0 via $(NetMinimum)
with RollForward=LatestMajor so it runs on net10+ runtimes. ESRP CLI
dependencies are bundled in the tools/ directory of the package.

Multi-target the library for net10.0 and the bundled TFM so it can be
consumed by both the net10 tool and net11 tests/build tasks.

Fix trailing comma in signing-config.json that caused JSON parse failure
in build 2919455.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace PME/CORP terminology with federated/certificate auth
- Rewrite CLI with System.CommandLine for argument parsing
- Rename --esrp-id to --esrp-client-id, --esrp-client-id to --esrp-app-registration
- Fix federated token help text (no CORP/PME references)
- Rename signRegardless to alwaysSign across source, tests, and docs
- Add signing-config-schema.json with documented JSON schema
- Disable RecursiveSigning project in source build instead of TFM dedup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add PrivateAssets=all to all PackageReference and ProjectReference
  items in both library and CLI csproj files so the NuGet packages
  have no external package dependencies
- Add PushRecursiveSigningPackages pipeline stage that pushes
  RecursiveSigning packages to dotnet-sign-test-feed after build
- Update recursive-sign.yml template for renamed CLI options
  (--esrp-client-id, --esrp-app-registration)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace separate PushRecursiveSigningPackages stage with an inline
1ES.PublishNuget@1 step right after signing in the build job.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move schema from CLI project to RecursiveSigning/docs/ alongside
  the certificate calculator spec that documents the reader
- Add schema link to README docs list and calculator doc
- Add \ references in sample config files for editor validation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move sample-rules.json from CLI project to docs/samples/
- Add authenticode-and-nuget-signing.json sample with Authenticode
  + NuGet package signing (from pre-prototype config history)
- Update README to link to samples directory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add $comment annotations throughout both sample configs
- Add Microsoft400DualSign certificate with alwaysSign=true
  demonstrating dual-signing of pre-signed third-party binaries
- Add fileNameMappings entry showing how specific files use
  a different certificate than their extension default

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- DefaultFileAnalyzer: file-path AnalyzeAsync delegates to stream overload
- PEFileTypeAnalyzer: use Linq Any() in CanAnalyze, async CopyToAsync for
  non-seekable streams, remove swallowed exception in position restore
- ESRPCliSigningProvider: remove UnsafeRelaxedJsonEscaping from serializers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Includes:
- System.CommandLine CLI rewrite with renamed options
- signRegardless -> alwaysSign rename across all files
- PME/CORP terminology replaced with federated/certificate auth
- PrivateAssets=all for self-contained packages
- ExcludeFromSourceBuild on library project
- JSON signing config schema and sample configs with comments
- Code review cleanups: simplified analyzers, removed UnsafeRelaxedJsonEscaping
- PE file type analyzer and tests
- ESRP CLI result parser improvements

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mmitche
Copy link
Copy Markdown
Member Author

mmitche commented Mar 12, 2026

@mmitche mmitche closed this Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant