Skip to content

Commit 779057a

Browse files
committed
Rule for checking the signature.
An "end-to-end" style check that verifies whether or not Windows ultimately ends up trusting the binary.
1 parent 8b41976 commit 779057a

20 files changed

+289
-41
lines changed

AuthenticodeLint/AuthenticodeLint.csproj

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<WarningLevel>4</WarningLevel>
2626
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2727
<Prefer32Bit>false</Prefer32Bit>
28+
<UseVSHostingProcess>true</UseVSHostingProcess>
2829
</PropertyGroup>
2930
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
3031
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -37,6 +38,9 @@
3738
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
3839
<Prefer32Bit>false</Prefer32Bit>
3940
</PropertyGroup>
41+
<PropertyGroup>
42+
<ApplicationManifest>app.manifest</ApplicationManifest>
43+
</PropertyGroup>
4044
<ItemGroup>
4145
<Reference Include="System" />
4246
<Reference Include="System.Core" />
@@ -56,7 +60,9 @@
5660
<Compile Include="Interop\Crypt32.cs" />
5761
<Compile Include="Interop\CryptMsgSafeHandle.cs" />
5862
<Compile Include="Interop\LocalBufferSafeHandle.cs" />
63+
<Compile Include="Interop\Wintrust.cs" />
5964
<Compile Include="IRuleResultCollector.cs" />
65+
<Compile Include="KnownGuids.cs" />
6066
<Compile Include="KnownOids.cs" />
6167
<Compile Include="PublisherInformation.cs" />
6268
<Compile Include="Rfc3161Signature.cs" />
@@ -71,6 +77,7 @@
7177
<Compile Include="Rules\Sha2SignatureExistsRule.cs" />
7278
<Compile Include="Rules\SigningCertificateDigestAlgorithmRule.cs" />
7379
<Compile Include="Rules\TimestampedRule.cs" />
80+
<Compile Include="Rules\TrustedSignatureRule.cs" />
7481
<Compile Include="Signature.cs" />
7582
<Compile Include="SignatureExtractor.cs" />
7683
<Compile Include="Graph.cs" />
@@ -79,7 +86,9 @@
7986
<Compile Include="VerboseSignatureTextWriter.cs" />
8087
<Compile Include="XmlRuleResultCollector.cs" />
8188
</ItemGroup>
82-
<ItemGroup />
89+
<ItemGroup>
90+
<None Include="app.manifest" />
91+
</ItemGroup>
8392
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
8493
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
8594
Other similar extension points exist, see Microsoft.Common.targets.

AuthenticodeLint/CheckEngine.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ public IReadOnlyList<IAuthenticodeRule> GetRules()
2222
new TimestampedRule(),
2323
new PublisherInformationPresentRule(),
2424
new PublisherInformationUrlHttpsRule(),
25-
new SigningCertificateDigestAlgorithmRule()
25+
new SigningCertificateDigestAlgorithmRule(),
26+
new TrustedSignatureRule()
2627
};
2728
}
2829

29-
public RuleEngineResult RunAllRules(string file, Graph<Signature> signatures, List<IRuleResultCollector> collectors, HashSet<int> suppressedRuleIDs, bool verbose)
30+
public RuleEngineResult RunAllRules(string file, Graph<Signature> signatures, List<IRuleResultCollector> collectors, CheckConfiguration configuration)
3031
{
31-
32+
var verbose = configuration.Verbose;
33+
var suppressedRuleIDs = configuration.SuppressErrorIDs;
3234
var rules = GetRules();
3335
var engineResult = RuleEngineResult.AllPass;
3436
collectors.ForEach(c => c.BeginSet(file));
3537
foreach(var rule in rules)
3638
{
3739
RuleResult result;
38-
var verboseWriter = verbose ? new VerboseSignatureLogger() : SignatureLoggerBase.Null;
40+
var verboseWriter = verbose ? new VerboseSignatureLogger() : SignatureLogger.Null;
3941
if (signatures.Items.Count == 0)
4042
{
4143
result = RuleResult.Fail;
@@ -49,7 +51,7 @@ public RuleEngineResult RunAllRules(string file, Graph<Signature> signatures, Li
4951
}
5052
else
5153
{
52-
result = rule.Validate(signatures, verboseWriter);
54+
result = rule.Validate(signatures, verboseWriter, configuration, file);
5355
}
5456
}
5557
if (result != RuleResult.Pass)

AuthenticodeLint/ConfigurationValidator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@ public class CheckConfiguration
1010
public string ReportPath { get; }
1111
public bool Quiet { get; }
1212
public HashSet<int> SuppressErrorIDs { get; }
13-
1413
public bool Verbose { get; }
14+
public RevocationChecking RevocationMode {get;}
1515

16-
public CheckConfiguration(IReadOnlyList<string> inputPaths, string reportPath, bool quiet, HashSet<int> suppressErrorIDs, bool verbose)
16+
public CheckConfiguration(IReadOnlyList<string> inputPaths, string reportPath, bool quiet, HashSet<int> suppressErrorIDs, bool verbose, RevocationChecking revocationMode)
1717
{
1818
InputPaths = inputPaths;
1919
ReportPath = reportPath;
2020
Quiet = quiet;
2121
SuppressErrorIDs = suppressErrorIDs;
2222
Verbose = verbose;
23+
RevocationMode = revocationMode;
2324
}
2425
}
2526

AuthenticodeLint/Graph.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public Graph(IReadOnlyCollection<GraphItem<T>> items)
1111
Items = items;
1212
}
1313

14-
public static Graph<T> Empty { get; } = new Graph<T>(new List<GraphItem<T>>());
14+
public static Graph<T> Empty { get; } = new Graph<T>(System.Array.Empty<GraphItem<T>>());
1515

1616
public IEnumerable<T> VisitAll()
1717
{
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace AuthenticodeLint.Interop
5+
{
6+
internal static class Wintrust
7+
{
8+
[method: DllImport("wintrust.dll", EntryPoint = "WinVerifyTrustEx", CallingConvention = CallingConvention.Winapi, SetLastError = false)]
9+
[return: MarshalAs(UnmanagedType.I4)]
10+
public static extern unsafe int WinVerifyTrustEx
11+
(
12+
[param: In, MarshalAs(UnmanagedType.SysInt)] IntPtr hwnd,
13+
[param: In, MarshalAs(UnmanagedType.LPStruct)] Guid pgActionID,
14+
[param: In] WINTRUST_DATA* pWVTData
15+
);
16+
}
17+
18+
[type: StructLayout(LayoutKind.Sequential)]
19+
internal struct WINTRUST_DATA
20+
{
21+
public uint cbStruct;
22+
public IntPtr pPolicyCallbackData;
23+
public IntPtr pSIPClientData;
24+
public WinTrustDataUIChoice dwUIChoice;
25+
public WinTrustRevocationChecks fdwRevocationChecks;
26+
public WinTrustUnionChoice dwUnionChoice;
27+
public WINTRUST_DATA_UNION trustUnion;
28+
public WinTrustStateAction dwStateAction;
29+
public IntPtr hWVTStateData;
30+
public IntPtr pwszURLReference;
31+
public WinTrustProviderFlags dwProvFlags;
32+
public WinTrustUIContext dwUIContext;
33+
public IntPtr pSignatureSettings;
34+
}
35+
36+
[type: StructLayout(LayoutKind.Explicit)]
37+
internal unsafe struct WINTRUST_DATA_UNION
38+
{
39+
[field: FieldOffset(0)]
40+
public WINTRUST_FILE_INFO* pFile;
41+
}
42+
43+
[type: StructLayout(LayoutKind.Sequential)]
44+
internal struct WINTRUST_FILE_INFO
45+
{
46+
public uint cbStruct;
47+
public IntPtr pcwszFilePath;
48+
public IntPtr hFile;
49+
public IntPtr pgKnownSubject;
50+
}
51+
52+
53+
internal enum WinTrustDataUIChoice : uint
54+
{
55+
WTD_UI_ALL = 1,
56+
WTD_UI_NONE = 2,
57+
WTD_UI_NOBAD = 3,
58+
WTD_UI_NOGOOD = 4,
59+
}
60+
61+
internal enum WinTrustRevocationChecks : uint
62+
{
63+
WTD_REVOKE_NONE = 0,
64+
WTD_REVOKE_WHOLECHAIN = 1
65+
}
66+
67+
internal enum WinTrustUnionChoice : uint
68+
{
69+
WTD_CHOICE_FILE = 1,
70+
WTD_CHOICE_CATALOG = 2,
71+
WTD_CHOICE_BLOB = 3,
72+
WTD_CHOICE_SIGNER = 4,
73+
WTD_CHOICE_CERT = 5
74+
}
75+
76+
internal enum WinTrustStateAction : uint
77+
{
78+
WTD_STATEACTION_IGNORE = 0x00000000,
79+
WTD_STATEACTION_VERIFY = 0x00000001,
80+
WTD_STATEACTION_CLOSE = 0x00000002,
81+
WTD_STATEACTION_AUTO_CACHE = 0x00000003,
82+
WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004,
83+
}
84+
85+
[type: Flags]
86+
internal enum WinTrustProviderFlags : uint
87+
{
88+
NONE = 0,
89+
WTD_USE_IE4_TRUST_FLAG = 0x00000001,
90+
WTD_NO_IE4_CHAIN_FLAG = 0x00000002,
91+
WTD_NO_POLICY_USAGE_FLAG = 0x00000004,
92+
WTD_REVOCATION_CHECK_NONE = 0x00000010,
93+
WTD_REVOCATION_CHECK_END_CERT = 0x00000020,
94+
WTD_REVOCATION_CHECK_CHAIN = 0x00000040,
95+
WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x00000080,
96+
WTD_SAFER_FLAG = 0x00000100,
97+
WTD_HASH_ONLY_FLAG = 0x00000200,
98+
WTD_USE_DEFAULT_OSVER_CHECK = 0x00000400,
99+
WTD_LIFETIME_SIGNING_FLAG = 0x00000800,
100+
WTD_CACHE_ONLY_URL_RETRIEVAL = 0x00001000,
101+
WTD_DISABLE_MD2_MD4 = 0x00002000,
102+
WTD_MOTW = 0x00004000,
103+
}
104+
105+
internal enum WinTrustUIContext : uint
106+
{
107+
WTD_UICONTEXT_EXECUTE = 0,
108+
WTD_UICONTEXT_INSTALL = 1,
109+
}
110+
}

AuthenticodeLint/KnownGuids.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace AuthenticodeLint
4+
{
5+
internal static class KnownGuids
6+
{
7+
public static Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 { get; } = new Guid(0xaac56b, unchecked((short)0xcd44), 0x11d0, new byte[] { 0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee });
8+
}
9+
}

AuthenticodeLint/Program.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static int Main(string[] args)
2424
bool quiet = false;
2525
bool verbose = false;
2626
string report = null;
27+
var revocation = RevocationChecking.None;
2728
foreach(var parameter in parsedCommandLine)
2829
{
2930
if (parameter.Name == "in")
@@ -74,7 +75,7 @@ static int Main(string[] args)
7475
{
7576
if (!string.IsNullOrWhiteSpace(parameter.Value))
7677
{
77-
Console.Error.WriteLine($"-{parameter.Value} does not expect a value.");
78+
Console.Error.WriteLine($"-{parameter.Name} does not expect a value.");
7879
return ExitCodes.InvalidInputOrConfig;
7980
}
8081
quiet = true;
@@ -83,7 +84,7 @@ static int Main(string[] args)
8384
{
8485
if (!string.IsNullOrWhiteSpace(parameter.Value))
8586
{
86-
Console.Error.WriteLine($"-{parameter.Value} does not expect a value.");
87+
Console.Error.WriteLine($"-{parameter.Name} does not expect a value.");
8788
return ExitCodes.InvalidInputOrConfig;
8889
}
8990
verbose = true;
@@ -92,6 +93,19 @@ static int Main(string[] args)
9293
{
9394
report = parameter.Value;
9495
}
96+
else if (parameter.Name == "revocation")
97+
{
98+
if (string.IsNullOrWhiteSpace(parameter.Value))
99+
{
100+
Console.Error.WriteLine($"-{parameter.Name} requires a value if specified.");
101+
return ExitCodes.InvalidInputOrConfig;
102+
}
103+
if (!Enum.TryParse(parameter.Value, true, out revocation))
104+
{
105+
Console.Error.WriteLine($"-{parameter.Value} is an unrecognized revocation mode.");
106+
return ExitCodes.InvalidInputOrConfig;
107+
}
108+
}
95109
else
96110
{
97111
Console.Error.WriteLine($"-{parameter.Name} is an unknown parameter.");
@@ -103,7 +117,7 @@ static int Main(string[] args)
103117
Console.Error.WriteLine("Input is expected. See -help for usage.");
104118
return ExitCodes.InvalidInputOrConfig;
105119
}
106-
var configuration = new CheckConfiguration(inputs, report, quiet, suppress, verbose);
120+
var configuration = new CheckConfiguration(inputs, report, quiet, suppress, verbose, revocation);
107121

108122
if (!ConfigurationValidator.ValidateAndPrint(configuration, Console.Error))
109123
{
@@ -123,7 +137,7 @@ static int Main(string[] args)
123137
foreach (var file in inputs)
124138
{
125139
var signatures = extractor.Extract(file);
126-
if (CheckEngine.Instance.RunAllRules(file, signatures, collectors, suppress, verbose) != RuleEngineResult.AllPass)
140+
if (CheckEngine.Instance.RunAllRules(file, signatures, collectors, configuration) != RuleEngineResult.AllPass)
127141
{
128142
result = ExitCodes.ChecksFailed;
129143
}
@@ -138,21 +152,22 @@ static void ShowHelp()
138152
{
139153
Console.Out.WriteLine(@"Authenticode Linter
140154
141-
Checks the authenticode signature of your binaries.
155+
Checks the Authenticode signature of your binaries.
142156
143157
Usage: authlint.exe -in ""C:\path to an\executable.exe""
144158
145-
-in: A path to an executable, DLL, or MSI to lint. Can be specified multiple times. Supports wildcards. Required.
146-
-suppress: A comma separated list of error IDs to ignore. All checks are run if omitted. Optional.
147-
-q|quiet: Run quietly and do not print anything to the output. Optional.
148-
-report: A path to produce an XML file as a report. Optional.
149-
-verbose: Show verbose output.
159+
-in: A path to an executable, DLL, or MSI to lint. Can be specified multiple times. Supports wildcards. Required.
160+
-suppress: A comma separated list of error IDs to ignore. All checks are run if omitted. Optional.
161+
-q|quiet: Run quietly and do not print anything to the output. Optional.
162+
-report: A path to produce an XML file as a report. Optional.
163+
-verbose: Show verbose output. Cannot be combined with -quiet.
164+
-revocation: Specify how revocation checking is done. Valid values are none, offline, online. None is the default.
150165
151166
Exit codes:
152167
153168
0: All checks passed for all inputs, excluding any that were suppressed.
154169
1: Invalid input or configuration was specified.
155-
2: One or more checks failed, or the file is not authenticode signed.
170+
2: One or more checks failed, or the file is not Authenticode signed.
156171
");
157172
}
158173
}
@@ -164,4 +179,11 @@ internal static class ExitCodes
164179
public static int ChecksFailed { get; } = 2;
165180
public static int UnknownResults { get; } = 0xFF;
166181
}
182+
183+
public enum RevocationChecking
184+
{
185+
None,
186+
Offline,
187+
Online
188+
}
167189
}

AuthenticodeLint/PublisherInformation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ public PublisherInformation(AsnEncodedData data)
3535
Description = info.pwszProgramName;
3636
if (info.pMoreInfo != null)
3737
{
38-
var moreInfo = *info.pMoreInfo;
39-
switch(moreInfo.dwLinkChoice)
38+
var moreInfo = info.pMoreInfo;
39+
switch(moreInfo->dwLinkChoice)
4040
{
4141
case SpcLinkChoice.SPC_URL_LINK_CHOICE:
42-
UrlLink = Marshal.PtrToStringUni(moreInfo.linkUnion.pwszUrl);
42+
UrlLink = Marshal.PtrToStringUni(moreInfo->linkUnion.pwszUrl);
4343
break;
4444
case SpcLinkChoice.SPC_FILE_LINK_CHOICE:
45-
FileLink = Marshal.PtrToStringUni(moreInfo.linkUnion.pwszFile);
45+
FileLink = Marshal.PtrToStringUni(moreInfo->linkUnion.pwszFile);
4646
break;
4747
}
4848
}

AuthenticodeLint/Rfc3161Signature.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public abstract class SignatureBase : IDisposable
1010
{
1111
internal CMSG_SIGNER_INFO _signerInfo;
1212

13+
1314
protected SignatureBase(AsnEncodedData data)
1415
{
1516

@@ -61,7 +62,7 @@ public unsafe AuthenticodeSignature(AsnEncodedData data) : base(data)
6162
}
6263
else
6364
{
64-
throw new InvalidOperationException("Failed to read authenticode signature");
65+
throw new InvalidOperationException("Failed to read Authenticode signature");
6566
}
6667
}
6768
}

AuthenticodeLint/Rules/IAuthenticodeRule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ public interface IAuthenticodeRule
55
int RuleId { get; }
66
string ShortDescription { get; }
77
string RuleName { get; }
8-
RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter);
8+
RuleResult Validate(Graph<Signature> graph, SignatureLogger verboseWriter, CheckConfiguration configuration, string file);
99
}
1010
}

0 commit comments

Comments
 (0)