Skip to main content

Overview

Validation.PackageSigning.ProcessSignature is a .NET Framework 4.7.2 console executable that runs as a long-lived Azure Service Bus subscription listener. Its sole responsibility is to validate the cryptographic signatures embedded in .nupkg files. It is the authoritative service that decides whether a package’s author signature is trustworthy, whether an existing repository signature is acceptable, and whether the package must be stripped and re-signed before being made available to consumers. The job has two distinct operating modes controlled by a single boolean field in the incoming Service Bus message:
  • Processor mode (RequireRepositorySignature = false): Run before the repository-signing step. Strips unacceptable repository signatures and rejects bad author signatures. The package blob may be modified.
  • Validator mode (RequireRepositorySignature = true): Run after the repository-signing step. Repeats all the same checks and additionally requires a valid repository signature to be present. The package blob must not change.
Because it is a stateless Service Bus consumer, multiple instances can run in parallel without coordination.

Role in the NuGetGallery Ecosystem

The job sits at two checkpoints in the validation pipeline managed by NuGet.Services.Validation.Orchestrator. It reads and writes to three external stores: the Validation SQL DB (validator state, package signatures, certificate chain), the Gallery SQL DB (certificate display metadata, package signing certificate fingerprint), and Azure Blob Storage (raw DER-encoded certificate files and the modified .nupkg when signatures are stripped).

Key Files and Classes

Dependencies

NuGet Packages (via Validation.Common.Job and transitive references)

Internal Project References

Notable Patterns and Implementation Details

Two-pass validation design: The same executable handles both the pre-signing processor pass and the post-signing validator pass. The only difference is the RequireRepositorySignature flag on the incoming Service Bus message. This avoids duplicating signature validation logic across two separate services.
Repository signature stripping: When a package arrives with a repository signature whose V3 service index URL or signing certificate does not match the configured allow-list, the job rewrites the .nupkg blob using SignedPackageArchiveUtility.RemoveRepositorySignaturesAsync and uploads it with a READ+DELETE SAS token. The orchestrator then uses this new URL for the subsequent repository-signing step.
Dead-letter path for our own bad repo signatures: If a package already has a PackageSigningState record in the DB (meaning it was previously processed) and its repository signature fails verification, the job throws an InvalidOperationException rather than silently stripping the signature. This causes the Service Bus message to dead-letter and triggers an on-call alert. Cases 1–2 (customer tampering) are handled by stripping; cases 3–4 (our own signing failure) must not be silently recovered.
StripValidRepositorySignatures is a break-glass flag: When ProcessSignatureConfiguration.StripValidRepositorySignatures = true, even valid repository signatures are stripped on the pre-signing pass so that a fresh repository signature is applied. This should never be enabled in steady-state; it exists only for emergency re-signing scenarios.
Certificate extraction is one-way immutable for author signatures: SignaturePartsExtractor enforces that the end-certificate thumbprint and timestamp value for an author PackageSignature record can never change once written (allowSignatureChanges: false). Repository signature records are replaceable (allowSignatureChanges: true) because the repo signing certificate can rotate.
MinimalSignatureVerificationProvider is intentionally a no-op: The minimal verification pass uses a provider that always returns Valid. Its purpose is purely to exercise the NuGet client’s signature-format parsing so that any SignatureException is caught and translated into a user-facing ClientSigningVerificationFailure rather than propagating as an unhandled exception.
Queue-back optimization: After persisting the terminal validation status, the handler optionally enqueues a CheckValidator message back to the orchestrator’s Service Bus topic (guarded by IFeatureFlagService.IsQueueBackEnabled()). Without this, the orchestrator discovers completion only on its next polling cycle. With it, latency is reduced to near-real-time.
Idempotency via IValidatorStateService: On receipt of a message, the handler immediately checks the Validation DB. If the record is missing or in NotStarted state the message is retried (returns false). If the record is already in a terminal state the message is consumed without re-running validation (returns true). This makes the handler safe to receive duplicate messages.