Files
mRemoteNG/mRemoteV1/Tools/Authenticode.cs
2017-11-20 14:54:38 -05:00

295 lines
9.1 KiB
C#

#if !PORTABLE
using System;
using System.Windows.Forms;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Reflection;
using System.ComponentModel;
// ReSharper disable UnusedMember.Local
// ReSharper disable NotAccessedField.Local
// ReSharper disable UnusedAutoPropertyAccessor.Local
#pragma warning disable 414
#pragma warning disable 169
namespace mRemoteNG.Tools
{
public class Authenticode
{
#region Public Methods
public Authenticode(string filePath)
{
FilePath = filePath;
}
public StatusValue Verify()
{
var trustFileInfoPointer = default(IntPtr);
var trustDataPointer = default(IntPtr);
try
{
var fileInfo = new FileInfo(FilePath);
if (!fileInfo.Exists)
{
Status = StatusValue.FileNotExist;
return Status;
}
if (fileInfo.Length == 0)
{
Status = StatusValue.FileEmpty;
return Status;
}
if (RequireThumbprintMatch)
{
if (string.IsNullOrEmpty(ThumbprintToMatch))
{
Status = StatusValue.NoThumbprintToMatch;
return Status;
}
var certificate2 = new X509Certificate2(X509Certificate.CreateFromSignedFile(FilePath));
_thumbprint = certificate2.Thumbprint;
if (_thumbprint != ThumbprintToMatch)
{
Status = StatusValue.ThumbprintNotMatch;
return Status;
}
}
var trustFileInfo = new NativeMethods.WINTRUST_FILE_INFO {pcwszFilePath = FilePath};
trustFileInfoPointer = Marshal.AllocCoTaskMem(Marshal.SizeOf(trustFileInfo));
Marshal.StructureToPtr(trustFileInfo, trustFileInfoPointer, false);
var trustData = new NativeMethods.WINTRUST_DATA
{
dwUIChoice = (uint) Display,
fdwRevocationChecks = NativeMethods.WTD_REVOKE_WHOLECHAIN,
dwUnionChoice = NativeMethods.WTD_CHOICE_FILE,
pFile = trustFileInfoPointer,
dwStateAction = NativeMethods.WTD_STATEACTION_IGNORE,
dwProvFlags = NativeMethods.WTD_DISABLE_MD2_MD4,
dwUIContext = (uint) DisplayContext
};
trustDataPointer = Marshal.AllocCoTaskMem(Marshal.SizeOf(trustData));
Marshal.StructureToPtr(trustData, trustDataPointer, false);
var windowHandle = DisplayParentForm?.Handle ?? IntPtr.Zero;
_trustProviderErrorCode = NativeMethods.WinVerifyTrust(windowHandle, NativeMethods.WINTRUST_ACTION_GENERIC_VERIFY_V2, trustDataPointer);
// ReSharper disable once SwitchStatementMissingSomeCases
switch (_trustProviderErrorCode)
{
case NativeMethods.TRUST_E_NOSIGNATURE:
Status = StatusValue.NoSignature;
break;
case NativeMethods.TRUST_E_SUBJECT_NOT_TRUSTED:
break;
}
if (_trustProviderErrorCode != 0)
{
Status = StatusValue.TrustProviderError;
return Status;
}
Status = StatusValue.Verified;
return Status;
}
catch (Exception ex)
{
if (ex is CryptographicException)
{
var hResultProperty = ex.GetType().GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance);
if (hResultProperty != null)
{
var hResult = Convert.ToInt32(hResultProperty.GetValue(ex, null));
if (hResult == NativeMethods.CRYPT_E_NO_MATCH)
{
Status = StatusValue.NoSignature;
return Status;
}
}
}
// other exception, or hResultProperty is null or is not CRYPT_E_NO_MATCH
Status = StatusValue.UnhandledException;
Exception = ex;
return Status;
}
finally
{
if (trustDataPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(trustDataPointer);
}
if (trustFileInfoPointer != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(trustFileInfoPointer);
}
}
}
#endregion
#region Public Properties
private DisplayValue Display { get; set; } = DisplayValue.None;
private DisplayContextValue DisplayContext {get; set;}
private Form DisplayParentForm {get; set;}
internal Exception Exception {get; private set;}
private string FilePath {get; set;}
internal bool RequireThumbprintMatch { get; set;}
internal StatusValue Status { get; private set; }
public string GetStatusMessage()
{
// ReSharper disable once SwitchStatementMissingSomeCases
switch (Status)
{
case StatusValue.Verified:
return "The file was verified successfully.";
case StatusValue.FileNotExist:
return "The specified file does not exist.";
case StatusValue.FileEmpty:
return "The specified file is empty.";
case StatusValue.NoSignature:
return "The specified file is not digitally signed.";
case StatusValue.NoThumbprintToMatch:
return "A thumbprint match is required but no thumbprint to match against was specified.";
case StatusValue.ThumbprintNotMatch:
/* (char)0x2260 == the "not equal to" symbol (which I cannot print in here without changing the encoding of the file)
* Fancy...
*
* "<>" is fiarly cryptic for non-programers
* So is "!="
* "=/=" gets the job done, no?
* What about plain old English (or localized value): X is not equal to Y?
* :P
*/
return $"The thumbprint does not match. {_thumbprint} {(char) 0x2260} {ThumbprintToMatch}.";
case StatusValue.TrustProviderError:
var ex = new Win32Exception(_trustProviderErrorCode);
return $"The trust provider returned an error. {ex.Message}";
case StatusValue.UnhandledException:
return $"An unhandled exception occurred. {Exception.Message}";
default:
return "The status is unknown.";
}
}
private string _thumbprint;
internal string ThumbprintToMatch { get; set;}
private int _trustProviderErrorCode;
#endregion
#region Public Enums
private enum DisplayValue : uint
{
Unknown = 0,
All = NativeMethods.WTD_UI_ALL,
None = NativeMethods.WTD_UI_NONE,
NoBad = NativeMethods.WTD_UI_NOBAD,
NoGood = NativeMethods.WTD_UI_NOGOOD
}
private enum DisplayContextValue : uint
{
Execute = NativeMethods.WTD_UICONTEXT_EXECUTE,
Install = NativeMethods.WTD_UICONTEXT_INSTALL
}
public enum StatusValue
{
Unknown = 0,
Verified,
FileNotExist,
FileEmpty,
NoSignature,
NoThumbprintToMatch,
ThumbprintNotMatch,
TrustProviderError,
UnhandledException
}
#endregion
#region Protected Classes
private static class NativeMethods
{
// ReSharper disable InconsistentNaming
[DllImport("wintrust.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern int WinVerifyTrust([In()]IntPtr hWnd, [In(), MarshalAs(UnmanagedType.LPStruct)]Guid pgActionOID, [In()]IntPtr pWVTData);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class WINTRUST_DATA
{
public WINTRUST_DATA()
{
cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_DATA));
}
private uint cbStruct;
public IntPtr pPolicyCallbackData;
public IntPtr pSIPClientData;
public uint dwUIChoice;
public uint fdwRevocationChecks;
public uint dwUnionChoice;
public IntPtr pFile;
public uint dwStateAction;
public IntPtr hWVTStateData;
public IntPtr pwszURLReference;
public uint dwProvFlags;
public uint dwUIContext;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class WINTRUST_FILE_INFO
{
public WINTRUST_FILE_INFO()
{
cbStruct = (uint)Marshal.SizeOf(typeof(WINTRUST_FILE_INFO));
}
private uint cbStruct;
[MarshalAs(UnmanagedType.LPTStr)]public string pcwszFilePath;
public IntPtr hFile;
public IntPtr pgKnownSubject;
}
public const int CRYPT_E_NO_MATCH = unchecked ((int) 0x80092009);
public const int TRUST_E_SUBJECT_NOT_TRUSTED = unchecked ((int) 0x800B0004);
public const int TRUST_E_NOSIGNATURE = unchecked ((int) 0x800B0100);
public static readonly Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
public const uint WTD_CHOICE_FILE = 1;
public const uint WTD_DISABLE_MD2_MD4 = 0x2000;
public const uint WTD_REVOKE_WHOLECHAIN = 1;
public const uint WTD_STATEACTION_IGNORE = 0x0;
public const uint WTD_STATEACTION_VERIFY = 0x1;
public const uint WTD_STATEACTION_CLOSE = 0x2;
public const uint WTD_UI_ALL = 1;
public const uint WTD_UI_NONE = 2;
public const uint WTD_UI_NOBAD = 3;
public const uint WTD_UI_NOGOOD = 4;
public const uint WTD_UICONTEXT_EXECUTE = 0;
public const uint WTD_UICONTEXT_INSTALL = 1;
// ReSharper restore InconsistentNaming
}
#endregion
}
}
#endif