From 2b6ca9ce17588ccb7493b3ec38c307f90e192147 Mon Sep 17 00:00:00 2001 From: Ella Hathaway Date: Mon, 9 Mar 2026 17:35:29 +0000 Subject: [PATCH 1/2] Support detached signature verification for tars and zips --- src/SignCheck/Microsoft.SignCheck/Utils.cs | 5 +-- .../Verification/DebVerifier.cs | 2 +- ...LinuxPackageVerifier.cs => PgpVerifier.cs} | 34 ++++++++++++++++--- .../Verification/RpmVerifier.cs | 2 +- .../SignatureVerificationManager.cs | 2 +- .../Verification/TarVerifier.cs | 9 ++--- .../Verification/ZipVerifier.cs | 10 ++---- 7 files changed, 40 insertions(+), 24 deletions(-) rename src/SignCheck/Microsoft.SignCheck/Verification/{LinuxPackageVerifier.cs => PgpVerifier.cs} (73%) diff --git a/src/SignCheck/Microsoft.SignCheck/Utils.cs b/src/SignCheck/Microsoft.SignCheck/Utils.cs index fb5e306cc88..e1f0145f617 100644 --- a/src/SignCheck/Microsoft.SignCheck/Utils.cs +++ b/src/SignCheck/Microsoft.SignCheck/Utils.cs @@ -189,7 +189,7 @@ public static (int exitCode, string output, string error) RunBashCommand(string } /// - /// Download the Microsoft and Azure Linux public keys and import them into the keyring. + /// Download the Microsoft, Azure Linux, and .NET release public keys and import them into the keyring. /// public static void DownloadAndConfigurePublicKeys(string tempDir) { @@ -198,7 +198,8 @@ public static void DownloadAndConfigurePublicKeys(string tempDir) "https://packages.microsoft.com/keys/microsoft.asc", // Microsoft public key "https://packages.microsoft.com/keys/microsoft-2025.asc", // Microsoft public key for distributions that do not allow SHA1 "https://packages.microsoft.com/keys/microsoft-rolling.asc", // Non-SHA1 Microsoft public keys for non-Azure Linux distributions - "https://raw.githubusercontent.com/microsoft/azurelinux/3.0/SPECS/azurelinux-repos/MICROSOFT-RPM-GPG-KEY" // Azure linux public key + "https://raw.githubusercontent.com/microsoft/azurelinux/3.0/SPECS/azurelinux-repos/MICROSOFT-RPM-GPG-KEY", // Azure linux public key + "https://dot.net/release-key-2023", // .NET release public key }; foreach (string keyUrl in keyUrls) { diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/DebVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/DebVerifier.cs index 56faaaeff73..a90c9f263ee 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/DebVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/DebVerifier.cs @@ -9,7 +9,7 @@ namespace Microsoft.SignCheck.Verification { - public class DebVerifier : LinuxPackageVerifier + public class DebVerifier : PgpVerifier { public DebVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".deb") { } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/LinuxPackageVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs similarity index 73% rename from src/SignCheck/Microsoft.SignCheck/Verification/LinuxPackageVerifier.cs rename to src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs index 12986efc5b0..4459d82320e 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/LinuxPackageVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs @@ -10,12 +10,24 @@ namespace Microsoft.SignCheck.Verification { - public abstract class LinuxPackageVerifier : ArchiveVerifier + public abstract class PgpVerifier : ArchiveVerifier { - protected LinuxPackageVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension) { } + private bool _supportsDetachedSignature; + + protected PgpVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension, bool supportsDetachedSignature = false) + : base(log, exclusions, options, fileExtension) + { + _supportsDetachedSignature = supportsDetachedSignature; + } public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) - => VerifySupportedFileType(path, parent, virtualPath); + { + if (_supportsDetachedSignature && File.Exists(path + ".sig")) + { + return VerifySupportedFileType(path, parent, virtualPath); + } + return VerifyUnsupportedFileType(path, parent, virtualPath); + } /// /// Returns the paths to the signature document and the signable content. @@ -24,13 +36,25 @@ public override SignatureVerificationResult VerifySignature(string path, string /// /// /// - protected abstract (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string path, string tempDir); + protected virtual (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string path, string tempDir) + { + if (_supportsDetachedSignature) + { + string signature = $"{path}.sig"; + string signatureDocument = Path.Combine(tempDir, Path.GetFileName(signature)); + File.Copy(signature, signatureDocument, overwrite: true); + + return (signatureDocument, path); + } + + throw new InvalidOperationException("GetSignatureDocumentAndSignableContent must be overridden for supported archive types that do not use detached signatures."); + } protected override bool IsSigned(string path, SignatureVerificationResult svr) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - throw new PlatformNotSupportedException("Linux package verification is not supported on Windows."); + throw new PlatformNotSupportedException("Pgp verification is not supported on Windows."); } string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/RpmVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/RpmVerifier.cs index 5e80985a668..fe4d4b6115a 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/RpmVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/RpmVerifier.cs @@ -11,7 +11,7 @@ namespace Microsoft.SignCheck.Verification { - public class RpmVerifier : LinuxPackageVerifier + public class RpmVerifier : PgpVerifier { public RpmVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options) : base(log, exclusions, options, ".rpm") { } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs index 0354e118acf..42af53b8db2 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs @@ -116,7 +116,7 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer AddFileVerifier(new NupkgVerifier(log, exclusions, options)); AddFileVerifier(new PortableExecutableVerifier(log, exclusions, options, ".dll")); AddFileVerifier(new XmlVerifier(log, exclusions, options)); - AddFileVerifier(new ZipVerifier(log, exclusions, options)); + AddFileVerifier(new ZipVerifier(log, exclusions, options, supportsDetachedSignature: true)); } /// diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs index 6f9d38697a0..d1ff0fa1d69 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs @@ -10,19 +10,16 @@ namespace Microsoft.SignCheck.Verification { - public class TarVerifier : ArchiveVerifier + public class TarVerifier : PgpVerifier { - public TarVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension) + public TarVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension, supportsDetachedSignature: true) { if (fileExtension != ".tar" && fileExtension != ".gz" && fileExtension != ".tgz") { - throw new ArgumentException("fileExtension must be .tar or .gz"); + throw new ArgumentException("fileExtension must be .tar, .gz, or .tgz"); } } - public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) - => VerifyUnsupportedFileType(path, parent, virtualPath); - protected override IEnumerable ReadArchiveEntries(string archivePath) { using (var fileStream = File.Open(archivePath, FileMode.Open)) diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs index 510a93ba8b9..a83b37f67a2 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs @@ -8,15 +8,9 @@ namespace Microsoft.SignCheck.Verification { - public class ZipVerifier : ArchiveVerifier + public class ZipVerifier : PgpVerifier { - public ZipVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension = ".zip") : base(log, exclusions, options, fileExtension) - { - - } - - public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) - => VerifyUnsupportedFileType(path, parent, virtualPath); + public ZipVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension = ".zip", bool supportsDetachedSignature = false) : base(log, exclusions, options, fileExtension, supportsDetachedSignature) { } protected override IEnumerable ReadArchiveEntries(string archivePath) { From 3b99430712c174462a8c0a03a68e1ad5a0b471d9 Mon Sep 17 00:00:00 2001 From: Ella Hathaway Date: Thu, 19 Mar 2026 16:48:12 +0000 Subject: [PATCH 2/2] Rename supportsDetachedSignature to signatureIsDetached in SignCheck verifiers The parameter name 'supportsDetachedSignature' implied capability support, but it actually controls whether verifiers look for a detached signature (.sig file) instead of a non-detached signature. Rename to 'signatureIsDetached' to better reflect the semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.SignCheck/Verification/PgpVerifier.cs | 10 +++++----- .../Verification/SignatureVerificationManager.cs | 2 +- .../Microsoft.SignCheck/Verification/TarVerifier.cs | 2 +- .../Microsoft.SignCheck/Verification/ZipVerifier.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs index 4459d82320e..1b199091708 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/PgpVerifier.cs @@ -12,17 +12,17 @@ namespace Microsoft.SignCheck.Verification { public abstract class PgpVerifier : ArchiveVerifier { - private bool _supportsDetachedSignature; + private bool _signatureIsDetached; - protected PgpVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension, bool supportsDetachedSignature = false) + protected PgpVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension, bool signatureIsDetached = false) : base(log, exclusions, options, fileExtension) { - _supportsDetachedSignature = supportsDetachedSignature; + _signatureIsDetached = signatureIsDetached; } public override SignatureVerificationResult VerifySignature(string path, string parent, string virtualPath) { - if (_supportsDetachedSignature && File.Exists(path + ".sig")) + if (_signatureIsDetached && File.Exists(path + ".sig")) { return VerifySupportedFileType(path, parent, virtualPath); } @@ -38,7 +38,7 @@ public override SignatureVerificationResult VerifySignature(string path, string /// protected virtual (string signatureDocument, string signableContent) GetSignatureDocumentAndSignableContent(string path, string tempDir) { - if (_supportsDetachedSignature) + if (_signatureIsDetached) { string signature = $"{path}.sig"; string signatureDocument = Path.Combine(tempDir, Path.GetFileName(signature)); diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs index 42af53b8db2..f595376002d 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs @@ -116,7 +116,7 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer AddFileVerifier(new NupkgVerifier(log, exclusions, options)); AddFileVerifier(new PortableExecutableVerifier(log, exclusions, options, ".dll")); AddFileVerifier(new XmlVerifier(log, exclusions, options)); - AddFileVerifier(new ZipVerifier(log, exclusions, options, supportsDetachedSignature: true)); + AddFileVerifier(new ZipVerifier(log, exclusions, options, signatureIsDetached: true)); } /// diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs index d1ff0fa1d69..2f6196a8af3 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/TarVerifier.cs @@ -12,7 +12,7 @@ namespace Microsoft.SignCheck.Verification { public class TarVerifier : PgpVerifier { - public TarVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension, supportsDetachedSignature: true) + public TarVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension) : base(log, exclusions, options, fileExtension, signatureIsDetached: true) { if (fileExtension != ".tar" && fileExtension != ".gz" && fileExtension != ".tgz") { diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs index a83b37f67a2..8233d529cdc 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/ZipVerifier.cs @@ -10,7 +10,7 @@ namespace Microsoft.SignCheck.Verification { public class ZipVerifier : PgpVerifier { - public ZipVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension = ".zip", bool supportsDetachedSignature = false) : base(log, exclusions, options, fileExtension, supportsDetachedSignature) { } + public ZipVerifier(Log log, Exclusions exclusions, SignatureVerificationOptions options, string fileExtension = ".zip", bool signatureIsDetached = false) : base(log, exclusions, options, fileExtension, signatureIsDetached) { } protected override IEnumerable ReadArchiveEntries(string archivePath) {