Skip to content

Commit

Permalink
Use new Windows signature APIs from Microsoft.Security.Extensions p…
Browse files Browse the repository at this point in the history
…ackage (PowerShell#17159)



Co-authored-by: Anam Navied <[email protected]>
Co-authored-by: Travis Plunk <[email protected]>
  • Loading branch information
3 people authored Apr 20, 2022
1 parent c2e11e9 commit 8d453da
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 268 deletions.
186 changes: 93 additions & 93 deletions src/Microsoft.PowerShell.Security/security/CertificateProvider.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<!-- the following package(s) are from the powershell org -->
<PackageReference Include="Microsoft.Management.Infrastructure" Version="2.0.0" />
<PackageReference Include="Microsoft.PowerShell.Native" Version="7.3.0-preview.1" />
<!-- Signing APIs -->
<PackageReference Include="Microsoft.Security.Extensions" Version="1.2.0" />
</ItemGroup>

<PropertyGroup>
Expand Down
199 changes: 82 additions & 117 deletions src/System.Management.Automation/security/Authenticode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
#pragma warning disable 1634, 1691
#pragma warning disable 56523

using Dbg = System.Management.Automation;
#if !UNIX
using Microsoft.Security.Extensions;
#endif
using System.IO;
using System.Management.Automation.Internal;
using System.Management.Automation.Security;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

using Dbg = System.Management.Automation;
using DWORD = System.UInt32;

namespace System.Management.Automation
Expand Down Expand Up @@ -275,12 +279,12 @@ internal static Signature GetSignature(string fileName, string fileContent)

if (fileContent == null)
{
// First, try to get the signature from the catalog signature APIs.
signature = GetSignatureFromCatalog(fileName);
// First, try to get the signature from the latest dotNet signing API.
signature = GetSignatureFromMSSecurityExtensions(fileName);
}

// If there is no signature or it is invalid, go by the file content
// with the older WinVerifyTrust APIs
// with the older WinVerifyTrust APIs.
if ((signature == null) || (signature.Status != SignatureStatus.Valid))
{
signature = GetSignatureFromWinVerifyTrust(fileName, fileContent);
Expand All @@ -289,147 +293,108 @@ internal static Signature GetSignature(string fileName, string fileContent)
return signature;
}

/// <summary>
/// Gets the file signature using the dotNet Microsoft.Security.Extensions package.
/// This supports both Windows catalog file signatures and embedded file signatures.
/// But it is not supported on all Windows platforms/skus, noteably Win7 and nanoserver.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
private static Signature GetSignatureFromCatalog(string filename)
private static Signature GetSignatureFromMSSecurityExtensions(string filename)
{
#if UNIX
return null;
#else
if (Signature.CatalogApiAvailable.HasValue && !Signature.CatalogApiAvailable.Value)
{
// Signature.CatalogApiAvailable would be set to false the first time it is detected that
// WTGetSignatureInfo API does not exist on the platform, or if the API is not functional on the target platform.
// Just return from the function instead of revalidating.
return null;
}

Signature signature = null;

Utils.CheckArgForNullOrEmpty(filename, "fileName");
SecuritySupport.CheckIfFileExists(filename);

try
Signature signature = null;
FileSignatureInfo fileSigInfo;
using (FileStream fileStream = File.OpenRead(filename))
{
using (FileStream stream = File.OpenRead(filename))
try
{
fileSigInfo = FileSignatureInfo.GetFromFileStream(fileStream);
System.Diagnostics.Debug.Assert(fileSigInfo is not null, "Returned FileSignatureInfo should never be null.");
}
catch (Exception)
{
NativeMethods.SIGNATURE_INFO sigInfo = new NativeMethods.SIGNATURE_INFO();
sigInfo.cbSize = (uint)Marshal.SizeOf(sigInfo);
// For any API error, enable fallback to WinVerifyTrust APIs.
Signature.CatalogApiAvailable = false;
return null;
}
}

IntPtr ppCertContext = IntPtr.Zero;
IntPtr phStateData = IntPtr.Zero;
DWORD error = GetErrorFromSignatureState(fileSigInfo.State);

try
{
int hresult = NativeMethods.WTGetSignatureInfo(filename, stream.SafeFileHandle.DangerousGetHandle(),
NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_SIGNED |
NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CATALOG_FIRST |
NativeMethods.SIGNATURE_INFO_FLAGS.SIF_AUTHENTICODE_SIGNED |
NativeMethods.SIGNATURE_INFO_FLAGS.SIF_BASE_VERIFICATION |
NativeMethods.SIGNATURE_INFO_FLAGS.SIF_CHECK_OS_BINARY,
ref sigInfo, ref ppCertContext, ref phStateData);

if (Utils.Succeeded(hresult))
{
DWORD error = GetErrorFromSignatureState(sigInfo.nSignatureState);

X509Certificate2 cert = null;

if (ppCertContext != IntPtr.Zero)
{
cert = new X509Certificate2(ppCertContext);

// Get the time stamper certificate if available
TryGetProviderSigner(phStateData, out IntPtr pProvSigner, out X509Certificate2 timestamperCert);
if (timestamperCert != null)
{
signature = new Signature(filename, error, cert, timestamperCert);
}
else
{
signature = new Signature(filename, error, cert);
}

switch (sigInfo.nSignatureType)
{
case NativeMethods.SIGNATURE_INFO_TYPE.SIT_AUTHENTICODE: signature.SignatureType = SignatureType.Authenticode; break;
case NativeMethods.SIGNATURE_INFO_TYPE.SIT_CATALOG: signature.SignatureType = SignatureType.Catalog; break;
}

if (sigInfo.fOSBinary == 1)
{
signature.IsOSBinary = true;
}
}
else
{
signature = new Signature(filename, error);
}

if (!Signature.CatalogApiAvailable.HasValue)
{
string productFile = Path.Combine(Utils.DefaultPowerShellAppBase, "Modules\\PSDiagnostics\\PSDiagnostics.psm1");
if (signature.Status != SignatureStatus.Valid)
{
if (string.Equals(filename, productFile, StringComparison.OrdinalIgnoreCase))
{
Signature.CatalogApiAvailable = false;
}
else
{
// ProductFile has to be Catalog signed. Hence validating
// to see if the Catalog API is functional using the ProductFile.
Signature productFileSignature = GetSignatureFromCatalog(productFile);
Signature.CatalogApiAvailable = (productFileSignature != null && productFileSignature.Status == SignatureStatus.Valid);
}
}
}
}
else
{
// If calling NativeMethods.WTGetSignatureInfo failed (returned a non-zero value), we still want to set Signature.CatalogApiAvailable to false.
Signature.CatalogApiAvailable = false;
}
}
finally
{
if (phStateData != IntPtr.Zero)
{
NativeMethods.FreeWVTStateData(phStateData);
}
if (fileSigInfo.SigningCertificate is null)
{
signature = new Signature(filename, error);
}
else
{
signature = fileSigInfo.TimestampCertificate is null ?
new Signature(filename, error, fileSigInfo.SigningCertificate) :
new Signature(filename, error, fileSigInfo.SigningCertificate, fileSigInfo.TimestampCertificate);
}

if (ppCertContext != IntPtr.Zero)
{
NativeMethods.CertFreeCertificateContext(ppCertContext);
}
}
}
switch (fileSigInfo.Kind)
{
case SignatureKind.None:
signature.SignatureType = SignatureType.None;
break;

case SignatureKind.Embedded:
signature.SignatureType = SignatureType.Authenticode;
break;

case SignatureKind.Catalog:
signature.SignatureType = SignatureType.Catalog;
break;

default:
System.Diagnostics.Debug.Fail("Signature type can only be None, Authenticode or Catalog.");
break;
}
catch (TypeLoadException)

signature.IsOSBinary = fileSigInfo.IsOSBinary;

if (signature.SignatureType == SignatureType.Catalog && !Signature.CatalogApiAvailable.HasValue)
{
// If we don't have WTGetSignatureInfo, don't return a Signature.
Signature.CatalogApiAvailable = false;
return null;
Signature.CatalogApiAvailable = fileSigInfo.State != SignatureState.Invalid;
}

return signature;
#endif
}

private static DWORD GetErrorFromSignatureState(NativeMethods.SIGNATURE_STATE state)
#if !UNIX
private static DWORD GetErrorFromSignatureState(SignatureState signatureState)
{
switch (state)
switch (signatureState)
{
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_MISSING: return Win32Errors.TRUST_E_NOSIGNATURE;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_UNSUPPORTED: return Win32Errors.TRUST_E_NOSIGNATURE;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNSIGNED_POLICY: return Win32Errors.TRUST_E_NOSIGNATURE;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_CORRUPT: return Win32Errors.TRUST_E_BAD_DIGEST;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_INVALID_POLICY: return Win32Errors.CRYPT_E_BAD_MSG;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_VALID: return Win32Errors.NO_ERROR;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_TRUSTED: return Win32Errors.NO_ERROR;
case NativeMethods.SIGNATURE_STATE.SIGNATURE_STATE_UNTRUSTED: return Win32Errors.TRUST_E_EXPLICIT_DISTRUST;

// Should not happen
case SignatureState.Unsigned:
return Win32Errors.TRUST_E_NOSIGNATURE;

case SignatureState.SignedAndTrusted:
return Win32Errors.NO_ERROR;

case SignatureState.SignedAndNotTrusted:
return Win32Errors.TRUST_E_EXPLICIT_DISTRUST;

case SignatureState.Invalid:
return Win32Errors.TRUST_E_BAD_DIGEST;

default:
System.Diagnostics.Debug.Fail("Should not get here - could not map SIGNATURE_STATE");
System.Diagnostics.Debug.Fail("Should not get here - could not map FileSignatureInfo.State");
return Win32Errors.TRUST_E_NOSIGNATURE;
}
}
#endif

private static Signature GetSignatureFromWinVerifyTrust(string fileName, string fileContent)
{
Expand Down
2 changes: 1 addition & 1 deletion src/System.Management.Automation/security/MshSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public sealed class Signature

// Three states:
// - True: we can rely on the catalog API to check catalog signature.
// - False: we cannot rely on the catalog API, either because it doesn't exist in the OS (win7),
// - False: we cannot rely on the catalog API, either because it doesn't exist in the OS (win7, nano),
// or it's not working properly (OneCore SKUs or dev environment where powershell might
// be updated/refreshed).
// - Null: it's not determined yet whether catalog API can be relied on or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ public static bool IsProductBinary(string file)
return true;
}

// WTGetSignatureInfo is used to verify catalog signature.
// WTGetSignatureInfo, via Microsoft.Security.Extensions, is used to verify catalog signature.
// On Win7, catalog API is not available.
// On OneCore SKUs like NanoServer/IoT, the API has a bug that makes it not able to find the
// corresponding catalog file for a given product file, so it doesn't work properly.
Expand Down
56 changes: 0 additions & 56 deletions src/System.Management.Automation/security/nativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1145,62 +1145,6 @@ internal static extern
DWORD idxCert
);

/// Return Type: HRESULT->LONG->int
///pszFile: PCWSTR->WCHAR*
///hFile: HANDLE->void*
///sigInfoFlags: SIGNATURE_INFO_FLAGS->Anonymous_5157c654_2076_48e7_9241_84ac648615e9
///psiginfo: SIGNATURE_INFO*
///ppCertContext: void**
///phWVTStateData: HANDLE*
[DllImportAttribute("wintrust.dll", EntryPoint = "WTGetSignatureInfo", CallingConvention = CallingConvention.StdCall)]
internal static extern int WTGetSignatureInfo([InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string pszFile, [InAttribute()] System.IntPtr hFile, SIGNATURE_INFO_FLAGS sigInfoFlags, ref SIGNATURE_INFO psiginfo, ref System.IntPtr ppCertContext, ref System.IntPtr phWVTStateData);

internal static void FreeWVTStateData(System.IntPtr phWVTStateData)
{
WINTRUST_DATA wtd = new WINTRUST_DATA();
DWORD dwResult = Win32Errors.E_FAIL;
IntPtr WINTRUST_ACTION_GENERIC_VERIFY_V2 = IntPtr.Zero;
IntPtr wtdBuffer = IntPtr.Zero;

Guid actionVerify =
new Guid("00AAC56B-CD44-11d0-8CC2-00C04FC295EE");

try
{
WINTRUST_ACTION_GENERIC_VERIFY_V2 =
Marshal.AllocCoTaskMem(Marshal.SizeOf(actionVerify));
Marshal.StructureToPtr(actionVerify,
WINTRUST_ACTION_GENERIC_VERIFY_V2,
false);

wtd.cbStruct = (DWORD)Marshal.SizeOf(wtd);
wtd.dwUIChoice = (DWORD)WintrustUIChoice.WTD_UI_NONE;
wtd.fdwRevocationChecks = 0;
wtd.dwUnionChoice = (DWORD)WintrustUnionChoice.WTD_CHOICE_BLOB;
wtd.dwStateAction = (DWORD)WintrustAction.WTD_STATEACTION_CLOSE;
wtd.hWVTStateData = phWVTStateData;

wtdBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(wtd));
Marshal.StructureToPtr(wtd, wtdBuffer, false);

// The GetLastWin32Error of this is checked, but PreSharp doesn't seem to be
// able to see that.
#pragma warning disable 56523
dwResult = WinVerifyTrust(
IntPtr.Zero,
WINTRUST_ACTION_GENERIC_VERIFY_V2,
wtdBuffer);
#pragma warning restore 56523
}
finally
{
Marshal.DestroyStructure<WINTRUST_DATA>(wtdBuffer);
Marshal.FreeCoTaskMem(wtdBuffer);
Marshal.DestroyStructure<Guid>(WINTRUST_ACTION_GENERIC_VERIFY_V2);
Marshal.FreeCoTaskMem(WINTRUST_ACTION_GENERIC_VERIFY_V2);
}
}

//
// stuff required for getting cert extensions
//
Expand Down
22 changes: 22 additions & 0 deletions test/powershell/engine/Security/FileSignature.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Describe "Windows platform file signatures" -Tags 'Feature' {

It "Verifies Get-AuthenticodeSignature returns correct signature for catalog signed file" -Skip:(!$IsWindows) {

if ($null -eq $env:windir) {
throw "Expected Windows platform environment path variable '%windir%' not available."
}

$filePath = Join-Path -Path $env:windir -ChildPath 'System32\ntdll.dll'
if (! (Test-Path -Path $filePath)) {
throw "Expected Windows PowerShell platform module path '$filePath' not found."
}

$signature = Get-AuthenticodeSignature -FilePath $filePath
$signature | Should -Not -BeNullOrEmpty
$signature.Status | Should -BeExactly 'Valid'
$signature.SignatureType | Should -BeExactly 'Catalog'
}
}
10 changes: 10 additions & 0 deletions tools/cgmanifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,16 @@
},
"DevelopmentDependency": false
},
{
"Component": {
"Type": "nuget",
"Nuget": {
"Name": "Microsoft.Security.Extensions",
"Version": "1.2.0"
}
},
"DevelopmentDependency": false
},
{
"Component": {
"Type": "nuget",
Expand Down

0 comments on commit 8d453da

Please sign in to comment.