Skip to content

Commit 7836d7c

Browse files
committed
Patch 283 (DynamoDS#10421)
* Add WinVerityTrust wrapper * Utilzie new WinTrustWrapper method for cert verification * Add negative test * Add Package with faked certificate * Create sub namespace for WinVerityInteropp * Update the WinTrustInterop * Upate method call name * Add specific exceptions to the CertificateVerfication class * formating (cherry picked from commit 7b93efe)
1 parent 143f1b9 commit 7836d7c

File tree

5 files changed

+242
-30
lines changed

5 files changed

+242
-30
lines changed

src/DynamoUtilities/CertificateVerification.cs

Lines changed: 202 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,194 @@
33
using System.IO;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Runtime.InteropServices;
67
using System.Security.Cryptography.X509Certificates;
78
using System.Text;
89
using System.Threading.Tasks;
910

1011
namespace DynamoUtilities
1112
{
13+
internal sealed class WinTrustInterop
14+
{
15+
#region WinTrustData struct field enums
16+
enum WinTrustDataUIChoice : uint
17+
{
18+
All = 1,
19+
None = 2,
20+
NoBad = 3,
21+
NoGood = 4
22+
}
23+
24+
enum WinTrustDataRevocationChecks : uint
25+
{
26+
None = 0x00000000,
27+
WholeChain = 0x00000001
28+
}
29+
30+
enum WinTrustDataChoice : uint
31+
{
32+
File = 1,
33+
Catalog = 2,
34+
Blob = 3,
35+
Signer = 4,
36+
Certificate = 5
37+
}
38+
39+
enum WinTrustDataStateAction : uint
40+
{
41+
Ignore = 0x00000000,
42+
Verify = 0x00000001,
43+
Close = 0x00000002,
44+
AutoCache = 0x00000003,
45+
AutoCacheFlush = 0x00000004
46+
}
47+
48+
[FlagsAttribute]
49+
enum WinTrustDataProvFlags : uint
50+
{
51+
UseIe4TrustFlag = 0x00000001,
52+
NoIe4ChainFlag = 0x00000002,
53+
NoPolicyUsageFlag = 0x00000004,
54+
RevocationCheckNone = 0x00000010,
55+
RevocationCheckEndCert = 0x00000020,
56+
RevocationCheckChain = 0x00000040,
57+
RevocationCheckChainExcludeRoot = 0x00000080,
58+
SaferFlag = 0x00000100, // Used by software restriction policies. Should not be used.
59+
HashOnlyFlag = 0x00000200,
60+
UseDefaultOsverCheck = 0x00000400,
61+
LifetimeSigningFlag = 0x00000800,
62+
CacheOnlyUrlRetrieval = 0x00001000, // affects CRL retrieval and AIA retrieval
63+
DisableMD2andMD4 = 0x00002000 // Win7 SP1+: Disallows use of MD2 or MD4 in the chain except for the root
64+
}
65+
66+
enum WinTrustDataUIContext : uint
67+
{
68+
Execute = 0,
69+
Install = 1
70+
}
71+
#endregion
72+
73+
#region WinTrust structures
74+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
75+
class WinTrustFileInfo
76+
{
77+
UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustFileInfo));
78+
IntPtr pszFilePath; // required, file name to be verified
79+
IntPtr hFile = IntPtr.Zero; // optional, open handle to FilePath
80+
IntPtr pgKnownSubject = IntPtr.Zero; // optional, subject type if it is known
81+
82+
public WinTrustFileInfo(String _filePath)
83+
{
84+
pszFilePath = Marshal.StringToCoTaskMemAuto(_filePath);
85+
}
86+
public void Dispose()
87+
{
88+
if(pszFilePath != IntPtr.Zero) {
89+
Marshal.FreeCoTaskMem(pszFilePath);
90+
pszFilePath = IntPtr.Zero;
91+
}
92+
}
93+
}
94+
95+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
96+
class WinTrustData
97+
{
98+
UInt32 StructSize = (UInt32)Marshal.SizeOf(typeof(WinTrustData));
99+
IntPtr PolicyCallbackData = IntPtr.Zero;
100+
IntPtr SIPClientData = IntPtr.Zero;
101+
// required: UI choice
102+
WinTrustDataUIChoice UIChoice = WinTrustDataUIChoice.None;
103+
// required: certificate revocation check options
104+
WinTrustDataRevocationChecks RevocationChecks = WinTrustDataRevocationChecks.None;
105+
// required: which structure is being passed in?
106+
WinTrustDataChoice UnionChoice = WinTrustDataChoice.File;
107+
// individual file
108+
IntPtr FileInfoPtr;
109+
WinTrustDataStateAction StateAction = WinTrustDataStateAction.Ignore;
110+
IntPtr StateData = IntPtr.Zero;
111+
String URLReference = null;
112+
WinTrustDataProvFlags ProvFlags = WinTrustDataProvFlags.RevocationCheckChainExcludeRoot;
113+
WinTrustDataUIContext UIContext = WinTrustDataUIContext.Execute;
114+
115+
// constructor for silent WinTrustDataChoice.File check
116+
public WinTrustData(WinTrustFileInfo _fileInfo)
117+
{
118+
// On Win7SP1+, don't allow MD2 or MD4 signatures
119+
if ((Environment.OSVersion.Version.Major > 6) ||
120+
((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor > 1)) ||
121+
((Environment.OSVersion.Version.Major == 6) && (Environment.OSVersion.Version.Minor == 1) && !String.IsNullOrEmpty(Environment.OSVersion.ServicePack)))
122+
{
123+
ProvFlags |= WinTrustDataProvFlags.DisableMD2andMD4;
124+
}
125+
126+
WinTrustFileInfo wtfiData = _fileInfo;
127+
FileInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(WinTrustFileInfo)));
128+
Marshal.StructureToPtr(wtfiData, FileInfoPtr, false);
129+
}
130+
public void Dispose()
131+
{
132+
if(FileInfoPtr != IntPtr.Zero) {
133+
Marshal.FreeCoTaskMem(FileInfoPtr);
134+
FileInfoPtr = IntPtr.Zero;
135+
}
136+
}
137+
}
138+
#endregion
139+
140+
enum WinVerifyTrustResult : uint
141+
{
142+
Success = 0,
143+
ProviderUnknown = 0x800b0001, // Trust provider is not recognized on this system
144+
ActionUnknown = 0x800b0002, // Trust provider does not support the specified action
145+
SubjectFormUnknown = 0x800b0003, // Trust provider does not support the form specified for the subject
146+
SubjectNotTrusted = 0x800b0004, // Subject failed the specified verification action
147+
FileNotSigned = 0x800B0100, // TRUST_E_NOSIGNATURE - File was not signed
148+
SubjectExplicitlyDistrusted = 0x800B0111, // Signer's certificate is in the Untrusted Publishers store
149+
SignatureOrFileCorrupt = 0x80096010, // TRUST_E_BAD_DIGEST - file was probably corrupt
150+
SubjectCertExpired = 0x800B0101, // CERT_E_EXPIRED - Signer's certificate was expired
151+
SubjectCertificateRevoked = 0x800B010C, // CERT_E_REVOKED Subject's certificate was revoked
152+
UntrustedRoot = 0x800B0109 // CERT_E_UNTRUSTEDROOT - A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider.
153+
}
154+
155+
public class WinTrust
156+
{
157+
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
158+
// GUID of the action to perform
159+
private const string WINTRUST_ACTION_GENERIC_VERIFY_V2 = "{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}";
160+
161+
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)]
162+
static extern WinVerifyTrustResult WinVerifyTrust(
163+
[In] IntPtr hwnd,
164+
[In] [MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID,
165+
[In] WinTrustData pWVTData
166+
);
167+
168+
// call WinTrust.WinVerifyTrust() to check embedded file signature
169+
public static bool VerifyEmbeddedSignature(string fileName)
170+
{
171+
WinTrustFileInfo winTrustFileInfo = null;
172+
WinTrustData winTrustData = null;
173+
174+
try
175+
{
176+
winTrustFileInfo = new WinTrustFileInfo(fileName);
177+
winTrustData = new WinTrustData(winTrustFileInfo);
178+
Guid guidAction = new Guid(WINTRUST_ACTION_GENERIC_VERIFY_V2);
179+
WinVerifyTrustResult result = WinVerifyTrust(INVALID_HANDLE_VALUE, guidAction, winTrustData);
180+
bool ret = (result == WinVerifyTrustResult.Success);
181+
return ret;
182+
}
183+
finally
184+
{
185+
// free the locally-held unmanaged memory in the data structures
186+
if (winTrustFileInfo != null) winTrustFileInfo.Dispose();
187+
if (winTrustData != null) winTrustData.Dispose();
188+
}
189+
}
190+
private WinTrust() { }
191+
}
192+
}
193+
12194
public class CertificateVerification
13195
{
14196
/// <summary>
@@ -21,45 +203,37 @@ public static bool CheckAssemblyForValidCertificate(string assemblyPath)
21203
//Verify the assembly exists
22204
if (!File.Exists(assemblyPath))
23205
{
24-
throw new Exception(String.Format(
25-
"A dll file was not found at {0}. No certificate was able to be verified.", assemblyPath));
206+
throw new FileNotFoundException(String.Format(
207+
"A dll file was not found at {0}. No certificate was able to be verified.", assemblyPath), assemblyPath);
26208
}
27209

28-
//Verify that you can load the assembly into a Reflection only context
29-
Assembly asm;
210+
//Verify the node library file has a verified signed certificate
30211
try
31212
{
32-
asm = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
213+
var validCert = WinTrustInterop.WinTrust.VerifyEmbeddedSignature(assemblyPath);
214+
if (validCert)
215+
{
216+
return true;
217+
}
33218
}
34219
catch
35220
{
36-
throw new Exception(String.Format(
37-
"A dll file found at {0} could not be loaded.", assemblyPath));
221+
throw new AssemblyCertificateCheckException(assemblyPath);
38222
}
39223

40-
//Verify the node library has a verified signed certificate
41-
X509Certificate cert;
42-
try
43-
{
44-
cert = X509Certificate.CreateFromSignedFile(assemblyPath);
45-
}
46-
catch
47-
{
48-
throw new Exception(String.Format(
49-
"A dll file found at {0} did not have a certificate attached.", assemblyPath));
50-
}
224+
throw new UnTrustedAssemblyException(assemblyPath);
225+
}
51226

52-
if (cert != null)
53-
{
54-
var cert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(cert);
55-
if (cert2.Verify())
56-
{
57-
return true;
58-
}
59-
}
227+
public class UnTrustedAssemblyException : Exception
228+
{
229+
public UnTrustedAssemblyException(string assemblyPath) : base(String.Format(
230+
"A dll file found at {0} did not have a signed certificate.", assemblyPath)) { }
231+
}
60232

61-
throw new Exception(String.Format(
62-
"A dll file found at {0} did not have a signed certificate.", assemblyPath));
233+
public class AssemblyCertificateCheckException : Exception
234+
{
235+
public AssemblyCertificateCheckException(string assemblyPath) : base(String.Format(
236+
"Could not verify the dll file found at {0} has a signed certificate.", assemblyPath)) { }
63237
}
64238
}
65239
}

test/Libraries/DynamoUtilitiesTests/CertificateVerificationTests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class CertificateVerificationTests
1414
{
1515
[Test]
1616
[Category("UnitTests")]
17-
public void CheckAssemblyForValidCertificateTest()
17+
public void CheckAssemblyForValidCertificateTest_ValidCertificate()
1818
{
1919
var executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
2020
var directory = new DirectoryInfo(executingDirectory);
@@ -24,5 +24,43 @@ public void CheckAssemblyForValidCertificateTest()
2424

2525
Assert.IsTrue(DynamoUtilities.CertificateVerification.CheckAssemblyForValidCertificate(testFilePath));
2626
}
27+
28+
[Test]
29+
[Category("UnitTests")]
30+
public void CheckAssemblyForValidCertificateTest_NoCertificate()
31+
{
32+
var executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
33+
var directory = new DirectoryInfo(executingDirectory);
34+
var testFilePath = Path.Combine(directory.Parent.Parent.Parent.FullName, "test", "pkgs_signed", "Unsigned Package", "bin", "Package.dll");
35+
36+
Assert.IsTrue(File.Exists(testFilePath));
37+
38+
Assert.Throws(
39+
typeof(CertificateVerification.UnTrustedAssemblyException),
40+
() =>
41+
{
42+
DynamoUtilities.CertificateVerification.CheckAssemblyForValidCertificate(testFilePath);
43+
}
44+
);
45+
}
46+
47+
[Test]
48+
[Category("UnitTests")]
49+
public void CheckAssemblyForValidCertificateTest_TransferredCertificate()
50+
{
51+
var executingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
52+
var directory = new DirectoryInfo(executingDirectory);
53+
var testFilePath = Path.Combine(directory.Parent.Parent.Parent.FullName, "test", "pkgs_signed", "Modfied Signed Package", "bin", "LegitAssemblyTransplanted.dll");
54+
55+
Assert.IsTrue(File.Exists(testFilePath));
56+
57+
Assert.Throws(
58+
typeof(CertificateVerification.UnTrustedAssemblyException),
59+
() =>
60+
{
61+
DynamoUtilities.CertificateVerification.CheckAssemblyForValidCertificate(testFilePath);
62+
}
63+
);
64+
}
2765
}
2866
}
Binary file not shown.
Binary file not shown.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"license":"","file_hash":null,"name":"Package","version":"1.0.0","description":"original package","group":"","keywords":null,"dependencies":[],"contents":"","engine_version":"2.1.0.7840","engine":"dynamo","engine_metadata":"","site_url":"","repository_url":"","contains_binaries":true,"node_libraries":["Package, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"]}
1+
{"license":"","file_hash":null,"name":"LegitAssemblyTransplanted","version":"1.0.0","description":"original package","group":"","keywords":null,"dependencies":[],"contents":"","engine_version":"2.1.0.7840","engine":"dynamo","engine_metadata":"","site_url":"","repository_url":"","contains_binaries":true,"node_libraries":["LegitAssemblyTransplanted, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"]}

0 commit comments

Comments
 (0)