Search
Close this search box.

C#: Determining if a file has a valid digital signature

One of the neat things Microsoft incorporated into Windows with the release of Internet Explorer 4 (which was provided for Windows 95 and Windows NT 4.0 with Service Pack 3) was the CryptoAPI, which provided not only services for secure hashing and stream ciphers, but also implemented Microsoft’s Authenticode (r) code-signing verification.

Authenticode is the technology that allows a Certification Authority (CA) such as Verisign to issue certificates to its clients in order to establish that software has been signed and is still authentic (that is, it has not been modified by any third parties).  This is beneficial for obvious reasons, of course – when you’re installing the Flash plugin, for instance, having an Authenticode signature verifies to you that the code is actually from Adobe, and that it hasn’t been modified for some malicious purpose by a third party (such as a hacker).

Windows Vista has incorporated this further by presenting User Account Control (UAC) dialogs based on the code-signing policy defined by the system.  When prompting for elevation, Windows Vista chooses one of four dialogs based on the security policy and code signature of the target executable.

As a software developer, verifying code integrity can be important; as a .NET developer, you of course have the Strong Name tool (sn.exe) to generate a private/public key pair with which to grant trust to assemblies (avoiding a trojan horse assembly in the process).  However, there may be times where we want to validate the digital signatures of non-CLR assemblies, macros, or some other entity that was digitally signed.  This will get you started by verifying the digital signature of a file; however, it can be used for other entities as described in the documentation.

For this we will use the WinVerifyTrust API.  WinVerifyTrust takes a handle and two pointers – I hope you’re excited for some new adventures in Platform Invoke!  The first thing I put together quickly was an UnmanagedPointer class – by implementing IDisposable, it helps me to avoid memory leaks.  Here’s the quick low-down:

#region UnmanagedPointer class
internal sealed class UnmanagedPointer : IDisposable {
  private IntPtr m_ptr;
  private AllocMethod m_meth;
  internal UnmanagedPointer(IntPtr ptr, AllocMethod method) {
    m_meth = method;
    m_ptr = ptr;
  }

  ~UnmanagedPointer() {
    Dispose(false);
  }

#region IDisposable Members
  private void Dispose(bool disposing) {
    if (m_ptr != IntPtr.Zero) {
      if (m_meth == AllocMethod.HGlobal) {
        Marshal.FreeHGlobal(m_ptr);
      } else if (m_meth == AllocMethod.CoTaskMem) {
        Marshal.FreeCoTaskMem(m_ptr);
      }
      m_ptr = IntPtr.Zero;
    }

    if (disposing) {
      GC.SuppressFinalize(this);
    }
  }

  public void Dispose() {
    Dispose(true);
  }

#endregion

  public static implicit operator IntPtr(UnmanagedPointer ptr) {
    return ptr.m_ptr;
  }
}

The AllocMethod enumeration simply specifies the HGlobal and CoTaskMem members, which indicates how Marshal originally allocated the memory.  With this class, I can now drop the memory blocks into using statements to have them automatically cleaned up, dodging a memory leak.

When using file lookups, you need to utilize the WINTRUST_FILE_INFO structure, which can be represented in C# as:

internal struct WINTRUST_FILE_INFO : IDisposable {
  public WINTRUST_FILE_INFO(string fileName, Guid subject) {
    cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_FILE_INFO));
    pcwszFilePath = fileName;

    if (subject != Guid.Empty) {
      pgKnownSubject = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
      Marshal.StructureToPtr(subject, pgKnownSubject, true);
    } else {
      pgKnownSubject = IntPtr.Zero;
    }
    hFile = IntPtr.Zero;
  }
  public uint cbStruct;
  [MarshalAs(UnmanagedType.LPTStr)]
  public string pcwszFilePath;
  public IntPtr hFile;
  public IntPtr pgKnownSubject;

#region IDisposable Members

  public void Dispose() {
    Dispose(true);
  }

  private void Dispose(bool disposing) {
    if (pgKnownSubject != IntPtr.Zero) {
      Marshal.DestroyStructure(this.pgKnownSubject, typeof(Guid));
      Marshal.FreeHGlobal(this.pgKnownSubject);
    }
  }

#endregion
}

Note that this class allocates unmanaged memory for the pgKnownSubject member – it is actually a pointer to a GUID structure, not the GUID structure itself.  Since we’re not making a P/Invoke call, but a marshalable structure, we can’t define a “ref” field.  I could have alternatively used a Guid* pointer type in place of that IntPtr, but I decided to stick with verifiable code.  (Note that this is also true in the upcoming code, where a WINTRUST_FILE_INFO pointer is one of the fields.

Finally, we can build out the actual structure used by the API call, the WINTRUST_DATA structure:

[StructLayout(LayoutKind.Sequential)]
internal struct WINTRUST_DATA : IDisposable {
  public WINTRUST_DATA(WINTRUST_FILE_INFO fileInfo) {
    this.cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_DATA));
    pInfoStruct =
        Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)));
    Marshal.StructureToPtr(fileInfo, pInfoStruct, true);
    this.dwUnionChoice = UnionChoice.File;

    pPolicyCallbackData = IntPtr.Zero;
    pSIPCallbackData = IntPtr.Zero;

    dwUIChoice = UiChoice.NoUI;
    fdwRevocationChecks = RevocationCheckFlags.None;
    dwStateAction = StateAction.Ignore;
    hWVTStateData = IntPtr.Zero;
    pwszURLReference = IntPtr.Zero;
    dwProvFlags = TrustProviderFlags.Safer;

    dwUIContext = UIContext.Execute;
  }

  public uint cbStruct;
  public IntPtr pPolicyCallbackData;
  public IntPtr pSIPCallbackData;
  public UiChoice dwUIChoice;
  public RevocationCheckFlags fdwRevocationChecks;
  public UnionChoice dwUnionChoice;
  public IntPtr pInfoStruct;
  public StateAction dwStateAction;
  public IntPtr hWVTStateData;
  private IntPtr pwszURLReference;
  public TrustProviderFlags dwProvFlags;
  public UIContext dwUIContext;

#region IDisposable Members

  public void Dispose() {
    Dispose(true);
  }

  private void Dispose(bool disposing) {
    if (dwUnionChoice == UnionChoice.File) {
      WINTRUST_FILE_INFO info = new WINTRUST_FILE_INFO();
      Marshal.PtrToStructure(pInfoStruct, info);
      info.Dispose();
      Marshal.DestroyStructure(pInfoStruct, typeof(WINTRUST_FILE_INFO));
    }

    Marshal.FreeHGlobal(pInfoStruct);
  }

#endregion
}

The WINTRUST_DATA structure defines a union where I defined the pInfoStruct field.  It’s possible to create something similar to a union in C#, by setting StructLayout as Explicit, and by putting each of the fields in the union at the same field offset.  The drawback is that each field needs an explicit offset, which makes the structure not cross-platform (as 32-bit and 64-bit platforms will require diferrent field offsets).  I decided again to go the path of the IntPtr and do the custom marshaling myself.  You should note I use several enumerations – these are all defined on the API specification for WINTRUST_DATA. 

FINALLY!  We’re at the point where we actually can define and call the method.  I do both of these within a class called Interop, and translate it in an external method.  Here are the definitions: 

internal static uint WinVerifyTrust(string fileName) {
  Guid wintrust_action_generic_verify_v2 =
      new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
  WINTRUST_FILE_INFO fileInfo = new WINTRUST_FILE_INFO(fileName, Guid.Empty);
  WINTRUST_DATA data = new WINTRUST_DATA(fileInfo);

  uint result = 0;

  using (
      UnmanagedPointer guidPtr = new UnmanagedPointer(
          Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid))),
          AllocMethod.HGlobal)) using (UnmanagedPointer wvtDataPtr =
                                           new UnmanagedPointer(
                                               Marshal.AllocHGlobal(
                                                   Marshal.SizeOf(
                                                       typeof(WINTRUST_DATA))),
                                               AllocMethod.HGlobal)) {
    IntPtr pGuid = guidPtr;
    IntPtr pData = wvtDataPtr;

    Marshal.StructureToPtr(wintrust_action_generic_verify_v2, pGuid, true);
    Marshal.StructureToPtr(data, pData, true);

    result = WinVerifyTrust(IntPtr.Zero, pGuid, pData);
  }

  return result;
}

The hardest part of utilizing this code is dealing with the myriad of return values (which I will not go over here).  Essentially, if the API returns 0, then the file is signed; if not, you can use Marshal.GetLastError() to determine the results.

Kudos go out to Microsoft for the example C program on which this article is based!

This article is part of the GWB Archives. Original Author: Robert Paveza

Related Posts