diff --git a/Directory.Packages.props b/Directory.Packages.props index efa446634113..6ac60e42a2e0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -56,6 +56,7 @@ + diff --git a/eng/Versions.props b/eng/Versions.props index ad6177c027de..d8e92c141220 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -45,6 +45,7 @@ 2.0.0-beta4.23407.1 2.0.0-preview.1.23601.1 3.2.2146 + 0.3.49-beta @@ -195,7 +196,7 @@ 9.0.0-beta.23607.2 4.18.4 1.3.2 - 6.0.0-beta.22262.1 + 8.0.0-beta.23607.1 .exe diff --git a/src/Cli/dotnet/Installer/Windows/InstallerBase.cs b/src/Cli/dotnet/Installer/Windows/InstallerBase.cs index 5a7e19f87e16..6ac423a25ea6 100644 --- a/src/Cli/dotnet/Installer/Windows/InstallerBase.cs +++ b/src/Cli/dotnet/Installer/Windows/InstallerBase.cs @@ -103,7 +103,7 @@ protected bool VerifySignatures /// /// /// - /// + /// Determines whether MSI signatures should be verified protected InstallerBase(InstallElevationContextBase elevationContext, ISetupLogger logger, bool verifySignatures) { ElevationContext = elevationContext; @@ -123,7 +123,7 @@ protected void Elevate() /// Checks the specified error code to determine whether it indicates a success result. If not, additional extended information /// is retrieved before throwing a . /// - /// The property will be set to if the error is either + /// The property will be set to if the error is either /// or . /// /// The error code to check. @@ -133,18 +133,7 @@ protected void ExitOnError(uint error, string message) { if (!Error.Success(error)) { - StringBuilder sb = new(2048); - NativeMethods.FormatMessage((uint)(FormatMessage.FromSystem | FormatMessage.IgnoreInserts), - IntPtr.Zero, error, 0, sb, (uint)sb.Capacity, IntPtr.Zero); - string errorDetail = sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); - string errorMessage = $"{message} Error: 0x{error:x8}."; - - if (!string.IsNullOrWhiteSpace(errorDetail)) - { - errorMessage += $" {errorDetail}"; - } - - throw new WorkloadException(error, errorMessage); + throw new WorkloadException($"{message} Error: 0x{error:x8}, {Marshal.GetPInvokeErrorMessage((int)error)}"); } // Once set to true, we retain restart information for the duration of the underlying command. diff --git a/src/Cli/dotnet/Installer/Windows/LocalizableStrings.resx b/src/Cli/dotnet/Installer/Windows/LocalizableStrings.resx index da4ab122f64b..896807677bf5 100644 --- a/src/Cli/dotnet/Installer/Windows/LocalizableStrings.resx +++ b/src/Cli/dotnet/Installer/Windows/LocalizableStrings.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - AuthentiCode signature for {0} does not belong to a trusted organization. + + The requested certificate chain policy could not be checked: {0} \ No newline at end of file diff --git a/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs b/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs index 413fd6237347..5b5e2a6e343a 100644 --- a/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs +++ b/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.Versioning; -using System.Security; -using System.Security.Cryptography.X509Certificates; +#if !DOT_NET_BUILD_FROM_SOURCE using Microsoft.DotNet.Installer.Windows.Security; -using Microsoft.Win32.Msi; +#endif +using Microsoft.DotNet.Workloads.Workload; using Newtonsoft.Json; namespace Microsoft.DotNet.Installer.Windows @@ -17,6 +17,10 @@ namespace Microsoft.DotNet.Installer.Windows internal class MsiPackageCache : InstallerBase { /// + /// Determines whether revocation checks can go online. + /// + private bool _allowOnlineRevocationChecks; + /// The root directory of the package cache where MSI workload packs are stored. /// public readonly string PackageCacheRoot; @@ -27,6 +31,7 @@ public MsiPackageCache(InstallElevationContextBase elevationContext, ISetupLogge PackageCacheRoot = string.IsNullOrWhiteSpace(packageCacheRoot) ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "dotnet", "workloads") : packageCacheRoot; + _allowOnlineRevocationChecks = SignCheck.AllowOnlineRevocationChecks(); } /// @@ -139,58 +144,36 @@ public bool TryGetMsiPathFromPackageData(string packageDataPath, out string msiP } /// - /// Verifies the AuthentiCode signature of an MSI package if the executing command itself is running - /// from a signed module. + /// Verifies that an MSI package contains an Authenticode signature that terminates in a trusted Microsoft root certificate. /// /// The path of the MSI to verify. private void VerifyPackageSignature(string msiPath) { if (VerifySignatures) { - bool isAuthentiCodeSigned = AuthentiCode.IsSigned(msiPath); - - // Need to capture the error now as other OS calls might change the last error. - uint lastError = !isAuthentiCodeSigned ? unchecked((uint)Marshal.GetLastWin32Error()) : Error.SUCCESS; - - bool isTrustedOrganization = AuthentiCode.IsSignedByTrustedOrganization(msiPath, AuthentiCode.TrustedOrganizations); - - if (isAuthentiCodeSigned && isTrustedOrganization) + // MSI and authenticode verification only applies to Windows. NET only supports Win7 and later. +#if !DOT_NET_BUILD_FROM_SOURCE +#pragma warning disable CA1416 + unsafe { - Log?.LogMessage($"Successfully verified AuthentiCode signature for {msiPath}."); - } - else - { - // Summarize the failure and then report additional details. - Log?.LogMessage($"Failed to verify signature for {msiPath}. AuthentiCode signed: {isAuthentiCodeSigned}, Trusted organization: {isTrustedOrganization}."); - IEnumerable certificates = AuthentiCode.GetCertificates(msiPath); + int result = Signature.IsAuthenticodeSigned(msiPath, _allowOnlineRevocationChecks); - // Dump all the certificates if there are any. - if (certificates.Any()) + if (result != 0) { - Log?.LogMessage($"Certificate(s):"); - - foreach (X509Certificate2 certificate in certificates) - { - Log?.LogMessage($" Subject={certificate.Subject}"); - Log?.LogMessage($" Issuer={certificate.Issuer}"); - Log?.LogMessage($" Not before={certificate.NotBefore}"); - Log?.LogMessage($" Not after={certificate.NotAfter}"); - Log?.LogMessage($" Thumbprint={certificate.Thumbprint}"); - Log?.LogMessage($" Algorithm={certificate.SignatureAlgorithm.FriendlyName}"); - } + ExitOnError((uint)result, $"Failed to verify Authenticode signature, package: {msiPath}, allow online revocation checks: {_allowOnlineRevocationChecks}"); } - if (!isAuthentiCodeSigned) - { - // If it was a WinTrust failure, we can exit using that error code and include a proper message from the OS. - ExitOnError(lastError, $"Failed to verify authenticode signature for {msiPath}."); - } + result = Signature.HasMicrosoftTrustedRoot(msiPath); - if (!isTrustedOrganization) + if (result != 0) { - throw new SecurityException(string.Format(LocalizableStrings.AuthentiCodeNoTrustedOrg, msiPath)); + ExitOnError((uint)result, $"Failed to verify the Authenticode signature terminates in a trusted Microsoft root certificate. Package: {msiPath}"); } + } + Log?.LogMessage($"Successfully verified Authenticode signature for {msiPath}"); +#pragma warning restore CA1416 +#endif } else { diff --git a/src/Cli/dotnet/Installer/Windows/Security/AuthentiCode.cs b/src/Cli/dotnet/Installer/Windows/Security/AuthentiCode.cs deleted file mode 100644 index 59aeb5161034..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/AuthentiCode.cs +++ /dev/null @@ -1,191 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel; -using System.Runtime.Versioning; -using System.Security.Cryptography; -using System.Security.Cryptography.Pkcs; -using System.Security.Cryptography.X509Certificates; - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Contains various utilities methods around verifying AuthentiCode signatures on Windows. - /// -#if NETCOREAPP - [SupportedOSPlatform("windows")] -#endif - internal static class AuthentiCode - { - /// - /// A set of trusted organizations used to verify the certificates associated with an AuthentiCode signature. - /// - public static readonly string[] TrustedOrganizations = { "Microsoft Corporation" }; - - /// - /// Object identifier value for nested signature. - /// - private const string OidNestedSignature = "1.3.6.1.4.1.311.2.4.1"; - - /// - /// Verifies the authenticode signature of the specified file. - /// - /// The full path of the file to verify. - /// if the signature is valid; otherwise. - public static bool IsSigned(string path) - { - WinTrustFileInfo fileInfo = new() - { - cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustFileInfo)), - pcwszFilePath = Path.GetFullPath(path), - hFile = IntPtr.Zero, - pgKnownSubject = IntPtr.Zero - }; - - WinTrustData data = new() - { - cbStruct = (uint)Marshal.SizeOf(typeof(WinTrustData)), - dwProvFlags = 0, - dwStateAction = StateAction.WTD_STATEACTION_IGNORE, - dwUIChoice = UIChoice.WTD_UI_NONE, - dwUIContext = 0, - dwUnionChoice = UnionChoice.WTD_CHOICE_FILE, - fdwRevocationChecks = RevocationChecks.WTD_REVOKE_NONE, - hWVTStateData = IntPtr.Zero, - pWinTrustInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WinTrustFileInfo))), - pPolicyCallbackData = IntPtr.Zero, - pSIPClientData = IntPtr.Zero, - pwszURLReference = IntPtr.Zero - }; - - Marshal.StructureToPtr(fileInfo, data.pWinTrustInfo, false); - - IntPtr pGuid = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))); - IntPtr pData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WinTrustData))); - Marshal.StructureToPtr(data, pData, true); - Marshal.StructureToPtr(NativeMethods.WINTRUST_ACTION_GENERIC_VERIFY_V2, pGuid, true); - - uint result = NativeMethods.WinVerifyTrust(IntPtr.Zero, pGuid, pData); - - Marshal.FreeHGlobal(pGuid); - Marshal.FreeHGlobal(pData); - Marshal.FreeHGlobal(data.pWinTrustInfo); - - // The return value is a LONG, not an HRESULT. - // Do not use HRESULT macros such as SUCCEEDED to determine whether the function succeeded. - // Instead, check the return value for equality to zero. - return result == 0; - } - - /// - /// Determines if the file contains a signature associated with one of the specified organizations. The primary certificate is queried - /// before looking at nested certificates. - /// - /// The path of the signed file. - /// A set of trusted organizations. - /// if organization described in the certificate subject matches any of the specified - /// organizations; otherwise. - internal static bool IsSignedByTrustedOrganization(string path, params string[] organizations) - { - try - { - IEnumerable certificates = GetCertificates(path); - - return certificates.Any(c => organizations.Any(o => c.Subject.Contains($"O={o}", StringComparison.OrdinalIgnoreCase))); - } - catch (CryptographicException) - { - return false; - } - } - - /// - /// Retrieves the certificates from each signature in the specified file, including nested signatures. - /// - /// The path of the file. - /// A collection of one or more certificates. - internal static List GetCertificates(string path) - { - List certificates = new(); - - try - { - certificates.Add(new X509Certificate2(X509Certificate.CreateFromSignedFile(path))); - } - catch (CryptographicException) - { - return certificates; - } - - SignedCms cms = CreateSignedCmsFromFile(path); - - foreach (CryptographicAttributeObject attribute in cms.SignerInfos[0].UnsignedAttributes) - { - if (attribute.Oid.Value.Equals(OidNestedSignature)) - { - foreach (AsnEncodedData value in attribute.Values) - { - SignedCms nestedCms = new(); - nestedCms.Decode(value.RawData); - - certificates.Add(nestedCms.Certificates[0]); - } - } - } - - return certificates; - } - - /// - /// Creates a object from all the certificate data in a file. - /// - /// The full path of the file to use. - /// - private static SignedCms CreateSignedCmsFromFile(string path) - { - int msgAndCertEncodingType; - int msgContentType; - int formatType; - - IntPtr certStore = IntPtr.Zero; - IntPtr phMessage = IntPtr.Zero; - IntPtr context = IntPtr.Zero; - IntPtr pvObject = Marshal.StringToHGlobalUni(path); - - try - { - if (!NativeMethods.CryptQueryObject(CryptQueryObjectType.File, pvObject, - CertQueryContentFlags.All, CertQueryFormatFlags.All, 0, - out msgAndCertEncodingType, out msgContentType, out formatType, ref certStore, ref phMessage, ref context)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - int cbData = 0; - - // Passing in NULL to pvData retrieves the size of the encoded message, allowing us to allocate a buffer and then - // call the function again to retrieve it. - if (!NativeMethods.CryptMsgGetParam(phMessage, Crypt32.CMSG_ENCODED_MESSAGE, 0, IntPtr.Zero, ref cbData)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - byte[] pvData = new byte[cbData]; - if (!NativeMethods.CryptMsgGetParam(phMessage, Crypt32.CMSG_ENCODED_MESSAGE, 0, pvData, ref cbData)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - var signedCms = new SignedCms(); - signedCms.Decode(pvData); - - return signedCms; - } - finally - { - Marshal.FreeHGlobal(pvObject); - Marshal.FreeHGlobal(phMessage); - } - } - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/CertQueryContentFlags.cs b/src/Cli/dotnet/Installer/Windows/Security/CertQueryContentFlags.cs deleted file mode 100644 index 1aa375d93c06..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/CertQueryContentFlags.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Flags describing different content objects that can be queried. - /// - [Flags] - public enum CertQueryContentFlags - { - /// - /// The content is a single certificate - /// - Certificate = 1 << Crypt32.CERT_QUERY_CONTENT_CERT, - - /// - /// The content is a single certificate trust list (CTL). - /// - CertificateTrustList = 1 << Crypt32.CERT_QUERY_CONTENT_CTL, - - /// - /// The content is a single certificate revocation list (CRL). - /// - CertificalRevocationList = 1 << Crypt32.CERT_QUERY_CONTENT_CRL, - - /// - /// The content is a serialized store. - /// - SerializedStore = 1 << Crypt32.CERT_QUERY_CONTENT_SERIALIZED_STORE, - - /// - /// The content is a single, serialized certificate. - /// - SerializedCertificate = 1 << Crypt32.CERT_QUERY_CONTENT_SERIALIZED_CERT, - - /// - /// The content is a single, serialized certificate trust list (CTL). - /// - SerializedCertificateTrustList = 1 << Crypt32.CERT_QUERY_CONTENT_SERIALIZED_CTL, - - /// - /// The content is a single, serialized certificate revocation list (CRL). - /// - SerializedCertificateRevocationList = 1 << Crypt32.CERT_QUERY_CONTENT_SERIALIZED_CRL, - - /// - /// The content is a PKCS #7 signed message. - /// - Pkcs7Signed = 1 << Crypt32.CERT_QUERY_CONTENT_PKCS7_SIGNED, - - /// - /// The content is a PKCS #7 unsigned message. - /// - Pkcs7Unsigned = 1 << Crypt32.CERT_QUERY_CONTENT_PKCS7_UNSIGNED, - - /// - /// The content is an embedded PKCS #7 signed message. - /// - Pkcs7SignedEmbed = 1 << Crypt32.CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED, - - /// - /// The content is a PKCS #10 message. - /// - Pkcs10 = 1 << Crypt32.CERT_QUERY_CONTENT_PKCS10, - - /// - /// The content is an encoded PFX blob. - /// - Pfx = 1 << Crypt32.CERT_QUERY_CONTENT_PFX, - - /// - /// An encoded CertificatePair (contains forward and/or reverse cross certs) - /// - CertificatePair = 1 << Crypt32.CERT_QUERY_CONTENT_CERT_PAIR, - - /// - /// The content is an encoded PFX blob and will be loaded subject to various conditions. - /// See this for more details. - /// - PfxAndLoad = 1 << Crypt32.CERT_QUERY_CONTENT_PFX_AND_LOAD, - - /// - /// The content can be any type, except . - /// - All = Certificate | CertificateTrustList | CertificalRevocationList | - SerializedStore | SerializedCertificate | SerializedCertificateTrustList | SerializedCertificateRevocationList | - Pkcs7Signed | Pkcs7Unsigned | Pkcs7SignedEmbed | Pkcs10 | Pfx | - CertificatePair - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/CertQueryFormatFlags.cs b/src/Cli/dotnet/Installer/Windows/Security/CertQueryFormatFlags.cs deleted file mode 100644 index 3ded8384eaad..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/CertQueryFormatFlags.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// The expected format of return types, expressed as a flag. - /// - public enum CertQueryFormatFlags : uint - { - /// - /// The content is in binary format. - /// - Binary = 1 << Crypt32.CERT_QUERY_FORMAT_BINARY, - - /// - /// The content is base64 encoded. - /// - Base64 = 1 << Crypt32.CERT_QUERY_FORMAT_BASE64_ENCODED, - - /// - /// The content is in ASCII hex-encoded with a "{ASN}" prefix. - /// - AsnAsciiHex = 1 << Crypt32.CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED, - - /// - /// The content can be returned in any format. - /// - All = Binary | Base64 | AsnAsciiHex - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/Crypt32.cs b/src/Cli/dotnet/Installer/Windows/Security/Crypt32.cs deleted file mode 100644 index 9478bae88b9f..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/Crypt32.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Defines various constants related to Windows cryptographic APIs. - /// - internal partial class Crypt32 - { - //encoded single certificate - internal const int CERT_QUERY_CONTENT_CERT = 1; - //encoded single CTL - internal const int CERT_QUERY_CONTENT_CTL = 2; - //encoded single CRL - internal const int CERT_QUERY_CONTENT_CRL = 3; - //serialized store - internal const int CERT_QUERY_CONTENT_SERIALIZED_STORE = 4; - //serialized single certificate - internal const int CERT_QUERY_CONTENT_SERIALIZED_CERT = 5; - //serialized single CTL - internal const int CERT_QUERY_CONTENT_SERIALIZED_CTL = 6; - //serialized single CRL - internal const int CERT_QUERY_CONTENT_SERIALIZED_CRL = 7; - //a PKCS#7 signed message - internal const int CERT_QUERY_CONTENT_PKCS7_SIGNED = 8; - //a PKCS#7 message, such as enveloped message. But it is not a signed message, - internal const int CERT_QUERY_CONTENT_PKCS7_UNSIGNED = 9; - //a PKCS7 signed message embedded in a file - internal const int CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED = 10; - //an encoded PKCS#10 - internal const int CERT_QUERY_CONTENT_PKCS10 = 11; - //an encoded PFX BLOB - internal const int CERT_QUERY_CONTENT_PFX = 12; - //an encoded CertificatePair (contains forward and/or reverse cross certs) - internal const int CERT_QUERY_CONTENT_CERT_PAIR = 13; - //an encoded PFX BLOB, which was loaded to phCertStore - internal const int CERT_QUERY_CONTENT_PFX_AND_LOAD = 14; - - internal const int CERT_QUERY_FORMAT_BINARY = 1; - internal const int CERT_QUERY_FORMAT_BASE64_ENCODED = 2; - internal const int CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3; - - // See https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptmsggetparam - public const int CMSG_TYPE_PARAM = 1; - public const int CMSG_CONTENT_PARAM = 2; - public const int CMSG_BARE_CONTENT_PARAM = 3; - public const int CMSG_INNER_CONTENT_TYPE_PARAM = 4; - public const int CMSG_SIGNER_COUNT_PARAM = 5; - public const int CMSG_SIGNER_INFO_PARAM = 6; - public const int CMSG_SIGNER_CERT_INFO_PARAM = 7; - public const int CMSG_SIGNER_HASH_ALGORITHM_PARAM = 8; - public const int CMSG_SIGNER_AUTH_ATTR_PARAM = 9; - public const int CMSG_SIGNER_UNAUTH_ATTR_PARAM = 10; - public const int CMSG_CERT_COUNT_PARAM = 11; - public const int CMSG_CERT_PARAM = 12; - public const int CMSG_CRL_COUNT_PARAM = 13; - public const int CMSG_CRL_PARAM = 14; - public const int CMSG_ENVELOPE_ALGORITHM_PARAM = 15; - public const int CMSG_RECIPIENT_COUNT_PARAM = 17; - public const int CMSG_RECIPIENT_INDEX_PARAM = 18; - public const int CMSG_RECIPIENT_INFO_PARAM = 19; - public const int CMSG_HASH_ALGORITHM_PARAM = 20; - public const int CMSG_HASH_DATA_PARAM = 21; - public const int CMSG_COMPUTED_HASH_PARAM = 22; - public const int CMSG_ENCRYPT_PARAM = 26; - public const int CMSG_ENCRYPTED_DIGEST = 27; - public const int CMSG_ENCODED_SIGNER = 28; - public const int CMSG_ENCODED_MESSAGE = 29; - public const int CMSG_VERSION_PARAM = 30; - public const int CMSG_ATTR_CERT_COUNT_PARAM = 31; - public const int CMSG_ATTR_CERT_PARAM = 32; - public const int CMSG_CMS_RECIPIENT_COUNT_PARAM = 33; - public const int CMSG_CMS_RECIPIENT_INDEX_PARAM = 34; - public const int CMSG_CMS_RECIPIENT_ENCRYPTED_KEY_INDEX_PARAM = 35; - public const int CMSG_CMS_RECIPIENT_INFO_PARAM = 36; - public const int CMSG_UNPROTECTED_ATTR_PARAM = 37; - public const int CMSG_SIGNER_CERT_ID_PARAM = 38; - public const int CMSG_CMS_SIGNER_INFO_PARAM = 39; - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/CryptQueryObjectType.cs b/src/Cli/dotnet/Installer/Windows/Security/CryptQueryObjectType.cs deleted file mode 100644 index 6e7ede4ba5d9..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/CryptQueryObjectType.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// The type of object to be queried. - /// - public enum CryptQueryObjectType : uint - { - /// - /// The object is stored in a file. - /// - File = 1, - - /// - /// The object is stored in memory. - /// - Blob = 2 - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/NativeMethods.cs b/src/Cli/dotnet/Installer/Windows/Security/NativeMethods.cs deleted file mode 100644 index c759ef536638..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/NativeMethods.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - internal class NativeMethods - { - /// - /// The GUID action ID for using the AuthentiCode policy provider (see softpub.h). - /// - public static readonly Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}"); - - [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern bool CryptMsgGetParam( - IntPtr hCryptMsg, - int dwParamType, - int dwIndex, - [In, Out] byte[] pvData, - ref int pcbData); - - [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern bool CryptMsgGetParam( - IntPtr hCryptMsg, - int dwParamType, - int dwIndex, - IntPtr pvData, - ref int pcbData); - - [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern bool CryptQueryObject(CryptQueryObjectType dwObjectType, - IntPtr pvObject, - CertQueryContentFlags dwExpectedContentTypeFlags, - CertQueryFormatFlags dwExpectedFormatTypeFlags, - int dwFlags, - out int pdwMsgAndCertEncodingType, - out int pdwContentType, - out int pdwFormatType, - ref IntPtr phCertStore, - ref IntPtr phMsg, - ref IntPtr ppvContext); - - [DllImport("wintrust.dll", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - public static extern uint WinVerifyTrust(IntPtr hWnd, IntPtr pgActionID, IntPtr pWinTrustData); - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/ProviderSettings.cs b/src/Cli/dotnet/Installer/Windows/Security/ProviderSettings.cs deleted file mode 100644 index 611dcd7777c8..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/ProviderSettings.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Trust provider settings. - /// - [Flags] - public enum ProviderSettings : uint - { - /// - /// The trust is verified in the same manner as implemented by Internet Explorer 4.0. - /// - WTD_USE_IE4_TRUST_FLAG = 0x00000001, - - /// - /// The Internet Explorer 4.0 chain functionality is not used. - /// - WTD_NO_IE4_CHAIN_FLAG = 0x00000002, - - /// - /// The default verification of the policy provider, such as code signing for Authenticode, is not performed, and the certificate is assumed valid for all usages. - /// - WTD_NO_POLICY_USAGE_FLAG = 0x00000004, - - /// - /// Revocation checking is not performed. - /// - WTD_REVOCATION_CHECK_NONE = 0x00000010, - - /// - /// Revocation checking is performed on the end certificate only. - /// - WTD_REVOCATION_CHECK_END_CERT = 0x00000020, - - /// - /// Revocation checking is performed on the entire certificate chain. - /// - WTD_REVOCATION_CHECK_CHAIN = 0x00000040, - - /// - /// Revocation checking is performed on the entire certificate chain, excluding the root certificate. - /// - WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x00000080, - - /// - /// Not supported. - /// - WTD_SAFER_FLAG = 0x00000100, - - /// - /// Only the hash is verified. - /// - WTD_HASH_ONLY_FLAG = 0x00000200, - - /// - /// The default operating system version checking is performed. This flag is only used for verifying catalog-signed files. - /// - WTD_USE_DEFAULT_OSVER_CHECK = 0x00000400, - - /// - /// If this flag is not set, all time stamped signatures are considered valid forever. Setting this flag limits the valid lifetime of the signature to the lifetime of the signing certificate. This allows time stamped signatures to expire. - /// - WTD_LIFETIME_SIGNING_FLAG = 0x00000800, - - /// - /// Use only the local cache for revocation checks. Prevents revocation checks over the network. This value is not supported on Windows XP. - /// - WTD_CACHE_ONLY_URL_RETRIEVAL = 0x00001000, - - /// - /// Disable the use of MD2 and MD4 hashing algorithms. If a file is signed by using MD2 or MD4 and if this flag is set, an NTE_BAD_ALGID error is returned. - /// This flag is only supported on Windows 7 SP1 and later. - /// - WTD_DISABLE_MD2_MD4 = 0x00002000, - - /// - /// If this flag is specified it is assumed that the file being verified has been downloaded from the web and has the Mark of the Web attribute. Policies that are meant to apply to Mark of the Web files will be enforced. - /// This flag is only supported on Windows 8.1 and later or system that installed KB2862966. - /// - WTD_MOTW = 0x00004000 - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/RevocationChecks.cs b/src/Cli/dotnet/Installer/Windows/Security/RevocationChecks.cs deleted file mode 100644 index 0c983c67ddef..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/RevocationChecks.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Defines options to specify additional revocation checks. - /// - public enum RevocationChecks : uint - { - /// - /// No additional revocation checking will be done when the WTD_REVOKE_NONE flag is used in conjunction with the HTTPSPROV_ACTION value set in the pgActionID parameter of the WinVerifyTrust function. To ensure the WinVerifyTrust function does not attempt any network retrieval when verifying code signatures, WTD_CACHE_ONLY_URL_RETRIEVAL must be set in the dwProvFlags parameter. - /// - WTD_REVOKE_NONE = 0, - - /// - /// Revocation checking will be done on the whole chain. - /// - WTD_REVOKE_WHOLECHAIN = 1 - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/Signature.cs b/src/Cli/dotnet/Installer/Windows/Security/Signature.cs new file mode 100644 index 000000000000..16cfe8ab7738 --- /dev/null +++ b/src/Cli/dotnet/Installer/Windows/Security/Signature.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Versioning; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Windows.Win32.Foundation; +using Windows.Win32.Security.Cryptography; +using Windows.Win32.Security.WinTrust; +using static Windows.Win32.PInvoke; + +namespace Microsoft.DotNet.Installer.Windows.Security +{ + /// + /// Contains methods for verifying Authenticode signatures on Windows. + /// +#if NETCOREAPP + [SupportedOSPlatform("windows5.1.2600")] +#endif + internal static class Signature + { + /// + /// Verifies that the certificate used to sign the specified file terminates in a trusted Microsoft root certificate. The policy check is performed against + /// the first simple chain. + /// + /// The path of the file to verify. + /// 0 if the policy could be checked. can be called to obtain more detail about the failure. + /// + /// This method does not perform any other chain validation like revocation checks, timestamping, etc. + internal static unsafe int HasMicrosoftTrustedRoot(string path) + { + // Create an X509Certificate2 instance so we can access the certificate context and create a chain context. + using X509Certificate2 certificate = new(path); + + // We don't use X509Chain because it doesn't support verifying the specific policy and because we defer + // that to the WinTrust provider as it performs timestamp and revocation checks. + HCERTCHAINENGINE HCCE_LOCAL_MACHINE = (HCERTCHAINENGINE)0x01; + CERT_CHAIN_PARA pChainPara = default; + CERT_CHAIN_CONTEXT* pChainContext = default; + CERT_CONTEXT* pCertContext = (CERT_CONTEXT*)certificate.Handle; + uint dwFlags = 0; + + try + { + if (!CertGetCertificateChain(HCCE_LOCAL_MACHINE, pCertContext, null, default, &pChainPara, dwFlags, null, &pChainContext)) + { + throw new CryptographicException(Marshal.GetPInvokeErrorMessage(Marshal.GetLastWin32Error())); + } + + CERT_CHAIN_POLICY_PARA policyCriteria = default; + CERT_CHAIN_POLICY_STATUS policyStatus = default; + + policyCriteria.cbSize = (uint)sizeof(CERT_CHAIN_POLICY_PARA); + policyCriteria.dwFlags = (CERT_CHAIN_POLICY_FLAGS)MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG; + + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_MICROSOFT_ROOT, pChainContext, &policyCriteria, &policyStatus)) + { + throw new CryptographicException(string.Format(LocalizableStrings.UnableToCheckCertificateChainPolicy, nameof(CERT_CHAIN_POLICY_MICROSOFT_ROOT))); + } + + return (int)policyStatus.dwError; + } + finally + { + CertFreeCertificateChain(pChainContext); + } + } + + /// + /// Verifies that the specified file is Authenticode signed. + /// + /// The path of the file to verify. + /// Allow revocation checks to go online. When set to , the cached certificate revocation list is used instead. + /// 0 if successful. can be called to obtain more detail about the failure. + /// See this example for more information. + /// A valid Authenticode signature does not establish trust. For example, Microsoft SHA1 signatures will return a positive result, even though their + /// root certificates are no longer trusted. This simply verifies that the Authenticode signature is valid. + /// + internal static unsafe int IsAuthenticodeSigned(string path, bool allowOnlineRevocationChecks = true) + { + fixed (char* p = Path.GetFullPath(path)) + { + WINTRUST_FILE_INFO fileInfo = new() + { + pcwszFilePath = p, + cbStruct = (uint)sizeof(WINTRUST_FILE_INFO), + }; + + Guid policyGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_DATA trustData = new() + { + cbStruct = (uint)sizeof(WINTRUST_DATA), + dwUIChoice = WINTRUST_DATA_UICHOICE.WTD_UI_NONE, + fdwRevocationChecks = WINTRUST_DATA_REVOCATION_CHECKS.WTD_REVOKE_WHOLECHAIN, + dwUnionChoice = WINTRUST_DATA_UNION_CHOICE.WTD_CHOICE_FILE, + dwStateAction = WINTRUST_DATA_STATE_ACTION.WTD_STATEACTION_VERIFY, + dwProvFlags = WINTRUST_DATA_PROVIDER_FLAGS.WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, + }; + + if (!allowOnlineRevocationChecks) + { + trustData.dwProvFlags |= WINTRUST_DATA_PROVIDER_FLAGS.WTD_CACHE_ONLY_URL_RETRIEVAL; + } + + trustData.Anonymous.pFile = &fileInfo; + + int lstatus = WinVerifyTrust((HWND)IntPtr.Zero, ref policyGuid, &trustData); + + // Release the hWVTStateData handle, but return the original status. + trustData.dwStateAction = WINTRUST_DATA_STATE_ACTION.WTD_STATEACTION_CLOSE; + _ = WinVerifyTrust((HWND)IntPtr.Zero, ref policyGuid, &trustData); + + return lstatus; + } + } + } +} diff --git a/src/Cli/dotnet/Installer/Windows/Security/StateAction.cs b/src/Cli/dotnet/Installer/Windows/Security/StateAction.cs deleted file mode 100644 index 15a8b51e208b..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/StateAction.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Describes the action to be taken in relation to the state data. - /// - public enum StateAction : uint - { - /// - /// Ignore the hWVTStateData member. - /// - WTD_STATEACTION_IGNORE = 0, - - /// - /// Verify the trust of the object (typically a file) that is specified by the dwUnionChoice member. The hWVTStateData member will receive a handle to the state data. This handle must be freed by specifying the WTD_STATEACTION_CLOSE action in a subsequent call. - /// - WTD_STATEACTION_VERIFY = 1, - - /// - /// Free the hWVTStateData member previously allocated with the WTD_STATEACTION_VERIFY action. This action must be specified for every use of the WTD_STATEACTION_VERIFY action. - /// - WTD_STATEACTION_CLOSE = 2, - - /// - /// Write the catalog data to a WINTRUST_DATA structure and then cache that structure. This action only applies when the dwUnionChoice member contains WTD_CHOICE_CATALOG. - /// - WTD_STATEACTION_AUTO_CACHE = 3, - - /// - /// Flush any cached catalog data. This action only applies when the dwUnionChoice member contains WTD_CHOICE_CATALOG. - /// - WTD_STATEACTION_AUTO_CACHE_FLUSH = 4 - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/UIChoice.cs b/src/Cli/dotnet/Installer/Windows/Security/UIChoice.cs deleted file mode 100644 index 24a44db65909..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/UIChoice.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Describes the type of user interface to display. - /// - public enum UIChoice : uint - { - /// - /// Display all UI. - /// - WTD_UI_ALL = 1, - - /// - /// Display no UI. - /// - WTD_UI_NONE = 2, - - /// - /// Do not display any negative UI. - /// - WTD_UI_NOBAD = 3, - - /// - /// Do not display any positive UI. - /// - WTD_UI_NOGOOD = 4 - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/UnionChoice.cs b/src/Cli/dotnet/Installer/Windows/Security/UnionChoice.cs deleted file mode 100644 index 02569f3dd2f9..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/UnionChoice.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Specifies the union member to use when verifying trust. - /// - public enum UnionChoice : uint - { - /// - /// Use the file pointed to by pFile. - /// - WTD_CHOICE_FILE = 1, - - /// - /// Use the catalog pointed to by pCatalog. - /// - WTD_CHOICE_CATALOG = 2, - - /// - /// Use the BLOB pointed to by pBlob. - /// - WTD_CHOICE_BLOB, - - /// - /// Use the WINTRUST_SGNR_INFO structure pointed to by pSgnr. - /// - WTD_CHOICE_SIGNER, - - /// - /// Use the certificate pointed to by pCert. - /// - WTD_CHOICE_CERT - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/WinTrustData.cs b/src/Cli/dotnet/Installer/Windows/Security/WinTrustData.cs deleted file mode 100644 index 218de3e193e5..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/WinTrustData.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// Structure used to pass information to trust providers when calling WinVerifyTrust. - /// See this for further details. - /// - [StructLayout(LayoutKind.Sequential)] - public struct WinTrustData - { - /// - /// The size, in bytes, of the structure. - /// - public uint cbStruct; - - /// - /// A pointer to a buffer used to pass policy-specific data to a policy provider. This field may be . - /// - public IntPtr pPolicyCallbackData; - - /// - /// Pointer to a buffer used to pass subject interface package data to a SIP provider. This field may be . - /// - public IntPtr pSIPClientData; - - /// - /// Specifies the type of interface to use. - /// - public UIChoice dwUIChoice; - - /// - /// Specifies certificate revocation check options that the selected policy provider can perform. - /// - public RevocationChecks fdwRevocationChecks; - - /// - /// Specifies the union member to be used. This determines the type of object for which trust will be verified. - /// acts as the union member. - /// - public UnionChoice dwUnionChoice; - - /// - /// Pointer to the object for which trust will be verified. This is a union member. See . - /// - public IntPtr pWinTrustInfo; - - /// - /// Specifies the action to be taken. - /// - public StateAction dwStateAction; - - /// - /// A handle to the state data. The contents depends on the value of . - /// - public IntPtr hWVTStateData; - - /// - /// Reserved. This field should be . - /// - public IntPtr pwszURLReference; - - /// - /// DWORD value that specifies trust provider settings. - /// - public ProviderSettings dwProvFlags; - - /// - /// A DWORD value that specifies the user interface context for the WinVerifyTrust function. - /// This causes the text in the Authenticode dialog box to match the action taken on the file. - /// - public uint dwUIContext; - } -} diff --git a/src/Cli/dotnet/Installer/Windows/Security/WinTrustFileInfo.cs b/src/Cli/dotnet/Installer/Windows/Security/WinTrustFileInfo.cs deleted file mode 100644 index 431424ba4a24..000000000000 --- a/src/Cli/dotnet/Installer/Windows/Security/WinTrustFileInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Installer.Windows.Security -{ - /// - /// The structure to use when calling WinVerifyTrust to verify an individual file. See - /// this for - /// furhter details. - /// - [StructLayout(LayoutKind.Sequential)] - public struct WinTrustFileInfo - { - /// - /// The size, in bytes, of the structure. - /// - public uint cbStruct; - - /// - /// The full path and file name of the file to verify. This field cannot be . - /// - [MarshalAs(UnmanagedType.LPTStr)] - public string pcwszFilePath; - - /// - /// Optional handle to an open file to be verified. The handle must have read permissions. - /// - public IntPtr hFile; - - /// - /// Optional pointer to a GUID structure that specifies the subject type. - /// - public IntPtr pgKnownSubject; - } -} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf index 100b70e341ea..c00aae5e30af 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - Podpis AuthentiCode pro {0} nepatří důvěryhodné organizaci. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf index a358d63a5369..108efca9275b 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - Die AuthentiCode-Signatur für {0} gehört nicht zu einer vertrauenswürdigen Organisation. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf index 49c6a6af789f..196614f4c45f 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - La firma AuthentiCode para {0} no pertenece a una organización de confianza. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf index 52c9042f7e63..d9453fce2c36 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - La signature AuthentiCode pour {0} n’appartient pas à une organisation approuvée. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf index 6caedf570426..5cdcdd9e9707 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - La firma AuthentiCode per {0} non appartiene a un'organizzazione attendibile. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf index 3d2140ab9b43..29bca91ae045 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - {0} の AuthentiCode 署名は、信頼されている組織に属していません。 + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf index 5504670c80dc..eff70df8d5b1 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - {0}에 대한 AuthentiCode 서명이 신뢰할 수 있는 조직에 속해 있지 않습니다. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf index a89b7b61b7f9..d47b4882d5bb 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - Podpis AuthentiCode dla {0} nie należy do zaufanej organizacji. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf index b9c9f365b741..457c0255912b 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - A assinatura AuthentiCode para {0} não pertence a uma organização confiável. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf index 4e7c7f1c3591..d952a0d46d26 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - Подпись AuthentiCode для {0} не принадлежит доверенной организации. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf index e61cd713ac68..341d602fc487 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - {0} için AuthentiCode imzası güvenilir bir kuruluşa ait değil. + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf index ba5dcba91dd5..3323173002c8 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - {0} 的 AuthentiCode 签名不属于受信任的组织。 + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf index e5793719fa5d..28ffbf5688f0 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf @@ -2,9 +2,9 @@ - - AuthentiCode signature for {0} does not belong to a trusted organization. - {0} 的 AuthentiCode 簽章不屬於信任的組織。 + + The requested certificate chain policy could not be checked: {0} + The requested certificate chain policy could not be checked: {0} diff --git a/src/Cli/dotnet/NativeMethods.txt b/src/Cli/dotnet/NativeMethods.txt new file mode 100644 index 000000000000..30816e485917 --- /dev/null +++ b/src/Cli/dotnet/NativeMethods.txt @@ -0,0 +1,15 @@ +// Methods +WinVerifyTrust +CertFreeCertificateChain +CertGetCertificateChain +CertVerifyCertificateChainPolicy + +// Structs, constants, enums +WINTRUST_DATA +WINTRUST_ACTION* +WTD* +CERT_STORE* +CERT_CHAIN_POLICY* +MICROSOFT_ROOT_CERT* +TRUST_E* +CERT_E* \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-workload/SignCheck.cs b/src/Cli/dotnet/commands/dotnet-workload/SignCheck.cs index 1bd0df8bc8f4..65071af75a9e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/SignCheck.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/SignCheck.cs @@ -2,34 +2,57 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; +#if !DOT_NET_BUILD_FROM_SOURCE using Microsoft.DotNet.Installer.Windows.Security; +#endif using Microsoft.Win32; namespace Microsoft.DotNet.Workloads.Workload { internal static class SignCheck { + internal static readonly string OnlineRevocationCheckPolicyKeyName = "AllowOnlineRevocationChecks"; + internal static readonly string VerifySignaturesPolicyKeyName = "VerifySignatures"; + + private static readonly string s_WorkloadPolicyKey = @"SOFTWARE\Policies\Microsoft\dotnet\Workloads"; + private static readonly string s_dotnet = Assembly.GetExecutingAssembly().Location; /// - /// Determines whether dotnet is signed. + /// Determines whether dotnet.dll is signed. /// - /// if dotnet is signed; otherwise. - public static bool IsDotNetSigned() => IsSigned(s_dotnet); + /// if dotnet is signed; otherwise, . + public static bool IsDotNetSigned() + { + if (OperatingSystem.IsWindows()) + { +#if !DOT_NET_BUILD_FROM_SOURCE + // API is only available on XP and Server 2003 or later versions. .NET requires Win7 minimum. +#pragma warning disable CA1416 + // We don't care about trust in this case, only whether or not the file has a signatue as that determines + // whether we'll trigger sign verification for workload operations. + return Signature.IsAuthenticodeSigned(s_dotnet, AllowOnlineRevocationChecks()) == 0; +#pragma warning restore CA1416 +#endif + } + + return false; + } /// - /// Determines whether the specified file is signed by a trusted organization. + /// Determines whether revocation checks can go online based on the global policy setting in the registry. /// - /// if file is signed; otherwise. - internal static bool IsSigned(string path) + /// if the policy key is absent or set to a non-zero value; if the policy key is set to 0. + public static bool AllowOnlineRevocationChecks() { if (OperatingSystem.IsWindows()) { - return AuthentiCode.IsSigned(path) && - AuthentiCode.IsSignedByTrustedOrganization(path, AuthentiCode.TrustedOrganizations); + using RegistryKey policyKey = Registry.LocalMachine.OpenSubKey(s_WorkloadPolicyKey); + + return ((int?)policyKey?.GetValue(OnlineRevocationCheckPolicyKeyName) ?? 1) != 0; } - return false; + return true; } /// @@ -40,9 +63,9 @@ public static bool IsWorkloadSignVerificationPolicySet() { if (OperatingSystem.IsWindows()) { - using RegistryKey policyKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Policies\Microsoft\dotnet\Workloads"); + using RegistryKey policyKey = Registry.LocalMachine.OpenSubKey(s_WorkloadPolicyKey); - return ((int?)policyKey?.GetValue("VerifySignatures") ?? 0) != 0; + return ((int?)policyKey?.GetValue(VerifySignaturesPolicyKeyName) ?? 0) != 0; } return false; diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs b/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs index a8e4253fb675..f9c8c127b1d3 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs @@ -101,7 +101,7 @@ public MsiInstallerBase(InstallElevationContextBase elevationContext, ISetupLogg bool verifySignatures, IReporter reporter = null) : base(elevationContext, logger, verifySignatures) { Cache = new MsiPackageCache(elevationContext, logger, verifySignatures); - RecordRepository = new RegistryWorkloadInstallationRecordRepository(elevationContext, logger, VerifySignatures); + RecordRepository = new RegistryWorkloadInstallationRecordRepository(elevationContext, logger, verifySignatures); UpdateAgent = new WindowsUpdateAgent(logger); Reporter = reporter; } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs index fedfb980cb07..a0337c77c884 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs @@ -52,7 +52,7 @@ public NetSdkMsiInstallerClient(InstallElevationContextBase elevationContext, AppDomain.CurrentDomain.ProcessExit += OnProcessExit; - Log?.LogMessage($"Executing: {Windows.GetProcessCommandLine()}, PID: {CurrentProcess.Id}, PPID: {ParentProcess.Id}"); + Log?.LogMessage($"Executing: {Microsoft.DotNet.Cli.Utils.Windows.GetProcessCommandLine()}, PID: {CurrentProcess.Id}, PPID: {ParentProcess.Id}"); Log?.LogMessage($"{nameof(IsElevated)}: {IsElevated}"); Log?.LogMessage($"{nameof(Is64BitProcess)}: {Is64BitProcess}"); Log?.LogMessage($"{nameof(RebootPending)}: {RebootPending}"); diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 847bac7ab3a6..97db1f85a572 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -12,6 +12,7 @@ Microsoft.DotNet.Cli false true + true @@ -22,6 +23,7 @@ + @@ -85,7 +87,7 @@ - + @@ -98,6 +100,7 @@ + diff --git a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/PackagesProps/.template.config/localize/templatestrings.en.json b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/PackagesProps/.template.config/localize/templatestrings.en.json index 632083074a76..92d7ebe4c289 100644 --- a/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/PackagesProps/.template.config/localize/templatestrings.en.json +++ b/template_feed/Microsoft.DotNet.Common.ItemTemplates/content/PackagesProps/.template.config/localize/templatestrings.en.json @@ -1,7 +1,7 @@ { "author": "Microsoft", "name": "MSBuild Directory.Packages.props file", - "description": "An empty Directory.Packages.props file which can be used to specify package version centrally", + "description": "An empty Directory.Packages.props file which can be used to specify package versions centrally", "symbols/inherit/displayName": "Inherit", "symbols/inherit/description": "If true, adds an Import for the closest Directory.Packages.props in the file system directory hierarchy. Directory.Packages.props don't inherit by default, so doing this allows you to build up a set of customizations folder-by-folder." -} +} \ No newline at end of file diff --git a/test/dotnet.Tests/WindowsInstallerTests.cs b/test/dotnet.Tests/WindowsInstallerTests.cs index 25e29a487048..ed8b8abac808 100644 --- a/test/dotnet.Tests/WindowsInstallerTests.cs +++ b/test/dotnet.Tests/WindowsInstallerTests.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 System.ComponentModel; using System.IO.Pipes; using System.Reflection; using System.Runtime.Versioning; @@ -10,7 +9,7 @@ namespace Microsoft.DotNet.Tests { - [SupportedOSPlatform("windows")] + [SupportedOSPlatform("windows5.1.2600")] public class WindowsInstallerTests { private static string s_testDataPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestData"); @@ -131,58 +130,38 @@ public void RelatedProductExcludesMinVersion(string minVersion, UpgradeAttribute } [WindowsOnlyTheory] - [InlineData("tampered.msi", false, "The digital signature of the object did not verify.")] - [InlineData("dual_signed.dll", true, "")] - [InlineData("dotnet_realsigned.exe", true, "")] - [InlineData("dotnet_fakesigned.exe", false, "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.")] - public void AuthentiCodeSignaturesCanBeVerified(string file, bool shouldBeSigned, string expectedError) + // This verifies E_TRUST_BAD_DIGEST (file was modified after being signed) + [InlineData(@"tampered.msi", -2146869232)] + [InlineData(@"dual_signed.dll", 0)] + [InlineData(@"dotnet_realsigned.exe", 0)] + // Signed by the .NET Foundation, terminates in a DigiCert root, so should be accepted by the Authenticode trust provider. + [InlineData(@"BootstrapperCore.dll", 0)] + // Old SHA1 certificate, but still a valid signature. + [InlineData(@"system.web.mvc.dll", 0)] + public void AuthentiCodeSignaturesCanBeVerified(string file, int expectedStatus) { - bool isSigned = AuthentiCode.IsSigned(Path.Combine(s_testDataPath, file)); - - Assert.Equal(shouldBeSigned, isSigned); - - if (!shouldBeSigned) - { - Assert.Equal(expectedError, new Win32Exception(Marshal.GetLastWin32Error()).Message); - } + int status = Signature.IsAuthenticodeSigned(Path.Combine(s_testDataPath, file)); + Assert.Equal(expectedStatus, status); } [WindowsOnlyTheory] - [InlineData("dotnet_realsigned.exe")] - [InlineData("dotnet_fakesigned.exe")] - public void IsSignedByTrustedOrganizationOnlyVerifiesTheSubjectOrganization(string file) - { - Assert.True(AuthentiCode.IsSignedByTrustedOrganization(Path.Combine(s_testDataPath, file), AuthentiCode.TrustedOrganizations)); - } - - [WindowsOnlyFact] - public void IsSignedBytTrustedOrganizationVerifiesNestedSignatures() - { - Assert.True(AuthentiCode.IsSignedByTrustedOrganization(Path.Combine(s_testDataPath, "dual_signed.dll"), - "Foo", "Bar", "Microsoft Corporation")); ; - Assert.True(AuthentiCode.IsSignedByTrustedOrganization(Path.Combine(s_testDataPath, "dual_signed.dll"), - "Foo", "Bar", "WiX Toolset (.NET Foundation)")); - } - - [WindowsOnlyFact] - public void GetCertificatesRetrievesNestedSignatures() - { - var certificates = AuthentiCode.GetCertificates(Path.Combine(s_testDataPath, "triple_signed.dll")).ToArray(); - - Assert.Equal("CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", certificates[0].Subject); - Assert.Equal("sha1RSA", certificates[0].SignatureAlgorithm.FriendlyName); - Assert.Equal("CN=Microsoft 3rd Party Application Component, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", certificates[1].Subject); - Assert.Equal("sha256RSA", certificates[1].SignatureAlgorithm.FriendlyName); - Assert.Equal("CN=Microsoft Corporation, OU=MOPR, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", certificates[2].Subject); - Assert.Equal("sha256RSA", certificates[2].SignatureAlgorithm.FriendlyName); - } - - [WindowsOnlyFact] - public void GetCertificatesRetrievesNothingForUnsignedFiles() + [InlineData(@"dotnet_realsigned.exe", 0)] + // Valid SHA1 signature, but no longer considered a trusted root certificate, should return CERT_E_UNTRUSTEDROOT. + [InlineData(@"system.web.mvc.dll", -2146762487)] + // The first certificate chain terminates in a non-Microsoft root so it fails the policy. Workloads do not currently support + // 3rd party installers. If we change that policy and we sign installers with the Microsoft 3rd Party certificate we will need to extract the nested + // signature and verify that at least one chain terminates in a Microsoft root. The WinTrust logic will also need to be updated to verify each + // chain. + [InlineData(@"dual_signed.dll", -2146762487)] + // DigiCert root should fail the policy check because it's not a trusted Microsoft root certificate. + [InlineData(@"BootstrapperCore.dll", -2146762487)] + // Digest will fail verification, BUT the root certificate in the chain is a trusted root. + [InlineData(@"tampered.msi", 0)] + public void ItVerifiesTrustedMicrosoftRootCertificateChainPolicy(string file, int expectedResult) { - var certificates = AuthentiCode.GetCertificates(Assembly.GetExecutingAssembly().Location); + int result = Signature.HasMicrosoftTrustedRoot(Path.Combine(s_testDataPath, file)); - Assert.Empty(certificates); + Assert.Equal(expectedResult, result); } private NamedPipeServerStream CreateServerPipe(string name)