diff --git a/src/SignCheck/Microsoft.SignCheck/Interop/WinCrypt.cs b/src/SignCheck/Microsoft.SignCheck/Interop/WinCrypt.cs index 92a6ebbf8eb..2b274b30ff3 100644 --- a/src/SignCheck/Microsoft.SignCheck/Interop/WinCrypt.cs +++ b/src/SignCheck/Microsoft.SignCheck/Interop/WinCrypt.cs @@ -161,6 +161,7 @@ public static class WinCrypt public const int CMSG_SIGNER_CERT_ID_PARAM = 38; public const int CMSG_CMS_SIGNER_INFO_PARAM = 39; + public const string SPC_INDIRECT_DATA_OBJID = "1.3.6.1.4.1.311.2.1.4"; public const string szOID_RSA_signingTime = "1.2.840.113549.1.9.5"; public const string szOID_RSA_counterSign = "1.2.840.113549.1.9.6"; public const string szOID_RFC3161_counterSign = "1.3.6.1.4.1.311.3.3.1"; diff --git a/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj b/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj index 5af714fc818..c1306736f46 100644 --- a/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj +++ b/src/SignCheck/Microsoft.SignCheck/Microsoft.DotNet.SignCheckLibrary.csproj @@ -42,19 +42,16 @@ - - + + + - - - - diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs index 3c0014bfa9e..6a437653615 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCode.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.SignCheck.Interop; using System; using System.Linq; using System.Collections.Generic; @@ -11,12 +10,23 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.Pkcs; +using Microsoft.SignCheck.Interop; +#if NET +using System.Reflection.PortableExecutable; +#endif namespace Microsoft.SignCheck.Verification { public static class AuthentiCode - { - public static uint IsSigned(string path) + { + public static bool IsSigned(string path, SignatureVerificationResult svr) => + string.IsNullOrEmpty(path) ? false : IsSignedInternal(path, svr); + + public static IEnumerable GetTimestamps(string path) => + string.IsNullOrEmpty(path) ? Enumerable.Empty() : GetTimestampsInternal(path); + +#if NETFRAMEWORK + private static bool IsSignedInternal(string path, SignatureVerificationResult svr) { WinTrustFileInfo fileInfo = new WinTrustFileInfo() { @@ -50,55 +60,23 @@ public static uint IsSigned(string path) Marshal.StructureToPtr(data, pData, true); Marshal.StructureToPtr(WinTrust.WINTRUST_ACTION_GENERIC_VERIFY_V2, pGuid, true); - uint result = WinTrust.WinVerifyTrust(IntPtr.Zero, pGuid, pData); + uint hrresult = WinTrust.WinVerifyTrust(IntPtr.Zero, pGuid, pData); Marshal.FreeHGlobal(pGuid); Marshal.FreeHGlobal(pData); - return result; - } - - /// - /// Searches the unsigned attributes in the counter signature for a timestamp token. - /// - /// - /// - public static IEnumerable GetTimestampsFromCounterSignature(AsnEncodedData unsignedAttribute) - { - var timestamps = new List(); - var rfc3161CounterSignature = new Pkcs9AttributeObject(unsignedAttribute); - SignedCms rfc3161Message = new SignedCms(); - rfc3161Message.Decode(rfc3161CounterSignature.RawData); - - foreach (SignerInfo rfc3161SignerInfo in rfc3161Message.SignerInfos) + // Log non-zero HRESULTs + if (hrresult != 0) { - if (String.Equals(rfc3161Message.ContentInfo.ContentType.Value, WinCrypt.szOID_TIMESTAMP_TOKEN, StringComparison.OrdinalIgnoreCase)) - { - var timestampToken = NuGet.Packaging.Signing.TstInfo.Read(rfc3161Message.ContentInfo.Content); - - var timeStamp = new Timestamp - { - SignedOn = timestampToken.GenTime.LocalDateTime, - EffectiveDate = Convert.ToDateTime(rfc3161SignerInfo.Certificate.GetEffectiveDateString()).ToLocalTime(), - ExpiryDate = Convert.ToDateTime(rfc3161SignerInfo.Certificate.GetExpirationDateString()).ToLocalTime(), - SignatureAlgorithm = rfc3161SignerInfo.Certificate.SignatureAlgorithm.FriendlyName - }; - - timestamps.Add(timeStamp); - } + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + svr.AddDetail(DetailKeys.Error, String.Format(SignCheckResources.ErrorHResult, hrresult, errorMessage)); } - return timestamps; + return hrresult == 0; } - public static IEnumerable GetTimestamps(string path) + private static IEnumerable GetTimestampsInternal(string path) { - if (String.IsNullOrEmpty(path)) - { - return null; - } - - var timestamps = new List(); int msgAndCertEncodingType; int msgContentType; int formatType; @@ -141,6 +119,104 @@ public static IEnumerable GetTimestamps(string path) var signedCms = new SignedCms(); signedCms.Decode(vData); + return ExtractTimestamps(signedCms); + } +#else + private static bool IsSignedInternal(string path, SignatureVerificationResult svr) + { + try + { + SignedCms signedCms = ReadSecurityInfo(path); + if (signedCms == null) + { + return false; + } + + if (signedCms.ContentInfo.ContentType.Value != WinCrypt.SPC_INDIRECT_DATA_OBJID) + { + throw new CryptographicException($"Invalid content type: {signedCms.ContentInfo.ContentType.Value}"); + } + + SignerInfoCollection signerInfos = signedCms.SignerInfos; + SignerInfo signerInfo = GetPrimarySignerInfo(signerInfos); + + // Check the signatures + signerInfo.CheckSignature(signedCms.Certificates, true); + signedCms.CheckSignature(signedCms.Certificates, true); + + return true; + } + catch (Exception ex) + { + svr.AddDetail(DetailKeys.Error, ex.Message); + return false; + } + } + + private static IEnumerable GetTimestampsInternal(string path) + { + SignedCms signedCms = ReadSecurityInfo(path); + if (signedCms == null) + { + return Enumerable.Empty(); + } + + return ExtractTimestamps(signedCms); + } + + private static SignedCms ReadSecurityInfo(string path) + { + using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (PEReader peReader = new PEReader(fs)) + { + var securityDirectory = peReader.PEHeaders.PEHeader.CertificateTableDirectory; + if (securityDirectory.RelativeVirtualAddress != 0 && securityDirectory.Size != 0) + { + int securityHeaderSize = 8; // 4(length of cert) + 2(cert revision) + 2(cert type) + if (securityDirectory.Size <= securityHeaderSize) + { + // No security entry - just the header + return null; + } + + // Skip the header + fs.Position = securityDirectory.RelativeVirtualAddress + securityHeaderSize; + byte[] securityEntry = new byte[securityDirectory.Size - securityHeaderSize]; + + // Ensure the stream has enough data to read + if (fs.Length < fs.Position + securityEntry.Length) + { + throw new CryptographicException($"File '{path}' is too small to contain a valid security entry."); + } + + // Read the security entry + fs.ReadExactly(securityEntry); + + // Decode the security entry + var signedCms = new SignedCms(); + signedCms.Decode(securityEntry); + return signedCms; + } + } + + return null; + } + + private static SignerInfo GetPrimarySignerInfo(SignerInfoCollection signerInfos) + { + int signerCount = signerInfos.Count; + if (signerCount != 1) + { + throw new CryptographicException($"Invalid number of signers: {signerCount}. Expected 1."); + } + + return signerInfos[0]; + } +#endif + + private static IEnumerable ExtractTimestamps(SignedCms signedCms) + { + var timestamps = new List(); // Timestamp information can be stored in multiple sections. // A single SHA1 stores the timestamp as a counter sign in the unsigned attributes // Multiple authenticode signatures will store additional information as a nested signature @@ -201,5 +277,32 @@ public static IEnumerable GetTimestamps(string path) return timestamps; } + private static IEnumerable GetTimestampsFromCounterSignature(AsnEncodedData unsignedAttribute) + { + var timestamps = new List(); + var rfc3161CounterSignature = new Pkcs9AttributeObject(unsignedAttribute); + SignedCms rfc3161Message = new SignedCms(); + rfc3161Message.Decode(rfc3161CounterSignature.RawData); + + foreach (SignerInfo rfc3161SignerInfo in rfc3161Message.SignerInfos) + { + if (String.Equals(rfc3161Message.ContentInfo.ContentType.Value, WinCrypt.szOID_TIMESTAMP_TOKEN, StringComparison.OrdinalIgnoreCase)) + { + var timestampToken = NuGet.Packaging.Signing.TstInfo.Read(rfc3161Message.ContentInfo.Content); + + var timeStamp = new Timestamp + { + SignedOn = timestampToken.GenTime.LocalDateTime, + EffectiveDate = Convert.ToDateTime(rfc3161SignerInfo.Certificate.GetEffectiveDateString()).ToLocalTime(), + ExpiryDate = Convert.ToDateTime(rfc3161SignerInfo.Certificate.GetExpirationDateString()).ToLocalTime(), + SignatureAlgorithm = rfc3161SignerInfo.Certificate.SignatureAlgorithm.FriendlyName + }; + + timestamps.Add(timeStamp); + } + } + + return timestamps; + } } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs index 73dc0297fa8..8a0a3fcf436 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/AuthentiCodeVerifier.cs @@ -42,17 +42,9 @@ public override SignatureVerificationResult VerifySignature(string path, string protected SignatureVerificationResult VerifyAuthentiCode(string path, string parent, string virtualPath) { var svr = new SignatureVerificationResult(path, parent, virtualPath); - uint hresult = AuthentiCode.IsSigned(path); - svr.IsAuthentiCodeSigned = hresult == 0; + svr.IsAuthentiCodeSigned = AuthentiCode.IsSigned(path, svr); svr.IsSigned = svr.IsAuthentiCodeSigned; - // Log non-zero HRESULTs - if (hresult != 0) - { - string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; - svr.AddDetail(DetailKeys.Error, String.Format(SignCheckResources.ErrorHResult, hresult, errorMessage)); - } - // TODO: Should only check if there is a signature, even if it's invalid if (VerifyAuthenticodeTimestamps) { diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs index 6444c37e96e..8059917efac 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/ExeVerifier.cs @@ -5,7 +5,9 @@ using System.IO; using System.Linq; using Microsoft.SignCheck.Logging; +#if NETFRAMEWORK using Microsoft.Tools.WindowsInstallerXml; +#endif namespace Microsoft.SignCheck.Verification { @@ -24,6 +26,7 @@ public override SignatureVerificationResult VerifySignature(string path, string if (VerifyRecursive) { +#if NETFRAMEWORK if (PEHeader.ImageSectionHeaders.Select(s => s.SectionName).Contains(".wixburn")) { Log.WriteMessage(LogVerbosity.Diagnostic, SignCheckResources.DiagSectionHeader, ".wixburn"); @@ -54,6 +57,9 @@ public override SignatureVerificationResult VerifySignature(string path, string unbinder.DeleteTempFiles(); } } +#else + Log.WriteMessage(LogVerbosity.Normal, $"Unable to verify contents of '{svr.FullPath}' on .NET Core."); +#endif } // TODO: Check for SFXCAB, IronMan, etc. @@ -64,9 +70,11 @@ public override SignatureVerificationResult VerifySignature(string path, string /// /// Event handler for WiX Burn to extract a bundle. /// +#if NETFRAMEWORK private void UnbinderEventHandler(object sender, MessageEventArgs e) { Log.WriteMessage(LogVerbosity.Detailed, String.Format("{0}|{1}|{2}|{3}", e.Id, e.Level, e.ResourceName, e.SourceLineNumbers)); } +#endif } } diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/PortableExecutableVerifier.cs b/src/SignCheck/Microsoft.SignCheck/Verification/PortableExecutableVerifier.cs index 8803441f108..1064b435b0a 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/PortableExecutableVerifier.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/PortableExecutableVerifier.cs @@ -55,6 +55,7 @@ public void VerifyStrongName(SignatureVerificationResult svr, PortableExecutable // NGEN/CrossGen don't preserve StrongName signatures. if (!svr.IsNativeImage) { +#if NETFRAMEWORK bool wasVerified = false; int hresult = StrongName.ClrStrongName.StrongNameSignatureVerificationEx(svr.FullPath, fForceVerification: true, pfWasVerified: out wasVerified); svr.IsStrongNameSigned = hresult == StrongName.S_OK; @@ -73,6 +74,10 @@ public void VerifyStrongName(SignatureVerificationResult svr, PortableExecutable svr.AddDetail(DetailKeys.StrongName, SignCheckResources.DetailPublicKeyToken, publicToken); } } +#else + svr.IsStrongNameSigned = false; + svr.AddDetail(DetailKeys.StrongName, $"StrongName signature verification is not supported on .NET Core."); +#endif } else { diff --git a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs index 9563d292dad..1c9e6b9e862 100644 --- a/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs +++ b/src/SignCheck/Microsoft.SignCheck/Verification/SignatureVerificationManager.cs @@ -6,9 +6,7 @@ using System.IO; using System.IO.Compression; using System.Linq; -# if NETFRAMEWORK using Microsoft.SignCheck.Interop.PortableExecutable; -#endif using Microsoft.SignCheck.Logging; namespace Microsoft.SignCheck.Verification @@ -89,18 +87,16 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer Options = options; #if NETFRAMEWORK - AddFileVerifier(new CabVerifier(log, exclusions, options, ".cab")); - AddFileVerifier(new PortableExecutableVerifier(log, exclusions, options, ".dll")); - AddFileVerifier(new ExeVerifier(log, exclusions, options, ".exe")); - AddFileVerifier(new JarVerifier(log, exclusions, options)); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".js")); - AddFileVerifier(new MsiVerifier(log, exclusions, options)); - AddFileVerifier(new MspVerifier(log, exclusions, options)); - AddFileVerifier(new MsuVerifier(log, exclusions, options)); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psd1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".psm1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1")); AddFileVerifier(new AuthentiCodeVerifier(log, exclusions, options, ".ps1xml")); + AddFileVerifier(new CabVerifier(log, exclusions, options, ".cab")); + AddFileVerifier(new JarVerifier(log, exclusions, options)); + AddFileVerifier(new MsiVerifier(log, exclusions, options)); + AddFileVerifier(new MspVerifier(log, exclusions, options)); + AddFileVerifier(new MsuVerifier(log, exclusions, options)); AddFileVerifier(new VsixVerifier(log, exclusions, options)); #else AddFileVerifier(new DebVerifier(log, exclusions, options)); @@ -111,8 +107,10 @@ public SignatureVerificationManager(Exclusions exclusions, Log log, SignatureVer AddFileVerifier(new TarVerifier(log, exclusions, options, ".gz")); AddFileVerifier(new RpmVerifier(log, exclusions, options)); #endif + AddFileVerifier(new ExeVerifier(log, exclusions, options, ".exe")); AddFileVerifier(new LzmaVerifier(log, exclusions, options)); 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)); }