Skip to content

Commit ada54b4

Browse files
committed
SHA2 chain validation.
Validates that the chain uses SHA2 EE and intermediate certificates. This does not fully validate the chain, such as the EKU, etc. This will instead be done as part of WinVerifyTrustEx in a later check. Fixes #2.
1 parent 5e5001c commit ada54b4

13 files changed

+171
-73
lines changed

AuthenticodeLint/AuthenticodeLint.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@
6969
<Compile Include="Rules\RuleResult.cs" />
7070
<Compile Include="Rules\Sha1PrimarySignatureRule.cs" />
7171
<Compile Include="Rules\Sha2SignatureExistsRule.cs" />
72+
<Compile Include="Rules\SigningCertificateDigestAlgorithmRule.cs" />
7273
<Compile Include="Rules\TimestampedRule.cs" />
74+
<Compile Include="Signature.cs" />
7375
<Compile Include="SignatureExtractor.cs" />
7476
<Compile Include="Graph.cs" />
7577
<Compile Include="SignerInfoExtensions.cs" />

AuthenticodeLint/CheckEngine.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
using System.Collections.Generic;
22
using AuthenticodeLint.Rules;
3-
using System.Security.Cryptography.Pkcs;
4-
using System;
5-
using System.IO;
63

74
namespace AuthenticodeLint
85
{
@@ -24,11 +21,12 @@ public IReadOnlyList<IAuthenticodeRule> GetRules()
2421
new NoWeakFileDigestAlgorithmsRule(),
2522
new TimestampedRule(),
2623
new PublisherInformationPresentRule(),
27-
new PublisherInformationUrlHttpsRule()
24+
new PublisherInformationUrlHttpsRule(),
25+
new SigningCertificateDigestAlgorithmRule()
2826
};
2927
}
3028

31-
public RuleEngineResult RunAllRules(string file, Graph<SignerInfo> signatures, List<IRuleResultCollector> collectors, HashSet<int> suppressedRuleIDs, bool verbose)
29+
public RuleEngineResult RunAllRules(string file, Graph<Signature> signatures, List<IRuleResultCollector> collectors, HashSet<int> suppressedRuleIDs, bool verbose)
3230
{
3331

3432
var rules = GetRules();

AuthenticodeLint/KnownOids.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
1-
namespace AuthenticodeLint
1+
using System.Security.Cryptography;
2+
3+
namespace AuthenticodeLint
24
{
35
internal static class KnownOids
46
{
5-
public static string SHA1 { get; } = "1.3.14.3.2.26";
6-
public static string SHA256 { get; } = "2.16.840.1.101.3.4.2.1";
7-
public static string SHA384 { get; } = "2.16.840.1.101.3.4.2.2";
8-
public static string SHA512 { get; } = "2.16.840.1.101.3.4.2.3";
9-
public static string MD5 { get; } = "1.2.840.113549.2.5";
10-
public static string MD4 { get; } = "1.2.840.113549.2.4";
11-
public static string MD2 { get; } = "1.2.840.113549.2.2";
7+
public const string SHA1 = "1.3.14.3.2.26";
8+
public const string SHA256 = "2.16.840.1.101.3.4.2.1";
9+
public const string SHA384 = "2.16.840.1.101.3.4.2.2";
10+
public const string SHA512 = "2.16.840.1.101.3.4.2.3";
11+
public const string MD5 = "1.2.840.113549.2.5";
12+
public const string MD4 = "1.2.840.113549.2.4";
13+
public const string MD2 = "1.2.840.113549.2.2";
14+
15+
public const string Rfc3161CounterSignature = "1.3.6.1.4.1.311.3.3.1";
16+
public const string AuthenticodeCounterSignature = "1.2.840.113549.1.9.6";
17+
public const string MessageDigest = "1.2.840.113549.1.9.4";
18+
public const string OpusInfo = "1.3.6.1.4.1.311.2.1.12";
19+
public const string CodeSigning = "1.3.6.1.5.5.7.3.3";
1220

1321

14-
public static string Rfc3161CounterSignature { get; } = "1.3.6.1.4.1.311.3.3.1";
15-
public static string AuthenticodeCounterSignature { get; } = "1.2.840.113549.1.9.6";
16-
public static string MessageDigest { get; } = "1.2.840.113549.1.9.4";
17-
public static string OpusInfo { get; } = "1.3.6.1.4.1.311.2.1.12";
22+
public const string md5RSA = "1.2.840.113549.1.1.4";
23+
public const string sha1DSA = "1.2.840.10040.4.3";
24+
public const string sha1RSA = "1.2.840.113549.1.1.5";
25+
public const string sha256RSA = "1.2.840.113549.1.1.11";
26+
public const string sha384RSA = "1.2.840.113549.1.1.12";
27+
public const string sha512RSA = "1.2.840.113549.1.1.13";
28+
public const string sha1ECDSA = "1.2.840.10045.4.1";
29+
public const string sha256ECDSA = "1.2.840.10045.4.3.2";
30+
public const string sha384ECDSA = "1.2.840.10045.4.3.3";
31+
public const string sha512ECDSA = "1.2.840.10045.4.3.4";
1832
}
1933
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
using System.Collections.Generic;
2-
using System.IO;
3-
using System.Security.Cryptography.Pkcs;
4-
5-
namespace AuthenticodeLint.Rules
1+
namespace AuthenticodeLint.Rules
62
{
73
public interface IAuthenticodeRule
84
{
95
int RuleId { get; }
106
string ShortDescription { get; }
117
string RuleName { get; }
12-
RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter);
8+
RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter);
139
}
1410
}

AuthenticodeLint/Rules/NoWeakFileDigestAlgorithmsRule.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Security.Cryptography.Pkcs;
2-
3-
namespace AuthenticodeLint.Rules
1+
namespace AuthenticodeLint.Rules
42
{
53
public class NoWeakFileDigestAlgorithmsRule : IAuthenticodeRule
64
{
@@ -10,25 +8,26 @@ public class NoWeakFileDigestAlgorithmsRule : IAuthenticodeRule
108

119
public string ShortDescription { get; } = "Checks for weak file digest algorithms.";
1210

13-
public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter)
11+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
1412
{
1513
var signatures = graph.VisitAll();
1614
var result = RuleResult.Pass;
1715
foreach(var signature in signatures)
1816
{
19-
if (signature.DigestAlgorithm.Value == KnownOids.MD2)
17+
var signatureInfo = signature.SignerInfo;
18+
if (signatureInfo.DigestAlgorithm.Value == KnownOids.MD2)
2019
{
21-
verboseWriter.LogSignatureMessage(signature, $"Uses the {nameof(KnownOids.MD2)} digest algorithm.");
20+
verboseWriter.LogSignatureMessage(signatureInfo, $"Uses the {nameof(KnownOids.MD2)} digest algorithm.");
2221
result = RuleResult.Fail;
2322
}
24-
else if (signature.DigestAlgorithm.Value == KnownOids.MD4)
23+
else if (signatureInfo.DigestAlgorithm.Value == KnownOids.MD4)
2524
{
26-
verboseWriter.LogSignatureMessage(signature, $"Uses the {nameof(KnownOids.MD4)} digest algorithm.");
25+
verboseWriter.LogSignatureMessage(signatureInfo, $"Uses the {nameof(KnownOids.MD4)} digest algorithm.");
2726
result = RuleResult.Fail;
2827
}
29-
else if (signature.DigestAlgorithm.Value == KnownOids.MD5)
28+
else if (signatureInfo.DigestAlgorithm.Value == KnownOids.MD5)
3029
{
31-
verboseWriter.LogSignatureMessage(signature, $"Uses the {nameof(KnownOids.MD5)} digest algorithm.");
30+
verboseWriter.LogSignatureMessage(signatureInfo, $"Uses the {nameof(KnownOids.MD5)} digest algorithm.");
3231
result = RuleResult.Fail;
3332
}
3433
}

AuthenticodeLint/Rules/PublisherInformationRule.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ public class PublisherInformationPresentRule : IAuthenticodeRule
77
{
88
public int RuleId { get; } = 10004;
99

10-
public string RuleName { get; } = "Publisher Information Rule";
10+
public string RuleName { get; } = "Publisher Information Present";
1111

1212
public string ShortDescription { get; } = "Checks that the signature provided publisher information.";
1313

14-
public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter)
14+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
1515
{
1616
var signatures = graph.VisitAll();
1717
var result = RuleResult.Pass;
18-
foreach(var signature in signatures)
18+
foreach (var signature in signatures)
1919
{
20+
var signatureInfo = signature.SignerInfo;
2021
PublisherInformation info = null;
21-
foreach(var attribute in signature.SignedAttributes)
22+
foreach (var attribute in signatureInfo.SignedAttributes)
2223
{
2324
if (attribute.Oid.Value == KnownOids.OpusInfo)
2425
{
@@ -29,24 +30,27 @@ public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseW
2930
if (info == null)
3031
{
3132
result = RuleResult.Fail;
32-
verboseWriter.LogSignatureMessage(signature, "Signature does not have any publisher information.");
33+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature does not have any publisher information.");
3334
}
3435
if (string.IsNullOrWhiteSpace(info.Description))
3536
{
3637
result = RuleResult.Fail;
37-
verboseWriter.LogSignatureMessage(signature, "Signature does not have an accompanying description.");
38+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature does not have an accompanying description.");
3839
}
3940

4041
if (string.IsNullOrWhiteSpace(info.UrlLink))
4142
{
4243
result = RuleResult.Fail;
43-
verboseWriter.LogSignatureMessage(signature, "Signature does not have an accompanying URL.");
44+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature does not have an accompanying URL.");
4445
}
45-
Uri uri;
46-
if (!Uri.TryCreate(info.UrlLink, UriKind.Absolute, out uri))
46+
else
4747
{
48-
result = RuleResult.Fail;
49-
verboseWriter.LogSignatureMessage(signature, "Signature's accompanying URL is not a valid URI.");
48+
Uri uri;
49+
if (!Uri.TryCreate(info.UrlLink, UriKind.Absolute, out uri))
50+
{
51+
result = RuleResult.Fail;
52+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature's accompanying URL is not a valid URI.");
53+
}
5054
}
5155
}
5256
return result;

AuthenticodeLint/Rules/PublisherInformationUrlHttpsRule.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ public class PublisherInformationUrlHttpsRule : IAuthenticodeRule
1111

1212
public string ShortDescription { get; } = "Checks that the signature uses HTTPS for the publisher's URL.";
1313

14-
public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter)
14+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
1515
{
1616
var signatures = graph.VisitAll();
1717
var result = RuleResult.Pass;
1818
foreach(var signature in signatures)
1919
{
20+
var signatureInfo = signature.SignerInfo;
2021
PublisherInformation info = null;
21-
foreach(var attribute in signature.SignedAttributes)
22+
foreach(var attribute in signatureInfo.SignedAttributes)
2223
{
2324
if (attribute.Oid.Value == KnownOids.OpusInfo)
2425
{
@@ -29,17 +30,17 @@ public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseW
2930
if (info == null)
3031
{
3132
result = RuleResult.Fail;
32-
verboseWriter.LogSignatureMessage(signature, "Signature does not have any publisher information.");
33+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature does not have any publisher information.");
3334
}
3435
if (string.IsNullOrWhiteSpace(info.UrlLink))
3536
{
3637
result = RuleResult.Fail;
37-
verboseWriter.LogSignatureMessage(signature, "Signature does not have an accompanying URL.");
38+
verboseWriter.LogSignatureMessage(signatureInfo, "Signature does not have an accompanying URL.");
3839
}
39-
if (!info.UrlLink.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
40+
else if (!info.UrlLink.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
4041
{
4142
result = RuleResult.Fail;
42-
verboseWriter.LogSignatureMessage(signature, $"Signature's publisher information URL \"{info.UrlLink}\" does not use the secure HTTPS scheme.");
43+
verboseWriter.LogSignatureMessage(signatureInfo, $"Signature's publisher information URL \"{info.UrlLink}\" does not use the secure HTTPS scheme.");
4344
}
4445
}
4546
return result;

AuthenticodeLint/Rules/Sha1PrimarySignatureRule.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Security.Cryptography.Pkcs;
1+
using System.Linq;
52

63
namespace AuthenticodeLint.Rules
74
{
@@ -13,17 +10,17 @@ public class Sha1PrimarySignatureRule : IAuthenticodeRule
1310

1411
public string ShortDescription { get; } = "Primary signature should be SHA1.";
1512

16-
public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter)
13+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
1714
{
1815
var primary = graph.Items.SingleOrDefault()?.Node;
1916
//There are zero signatures.
2017
if (primary == null)
2118
{
2219
return RuleResult.Fail;
2320
}
24-
if (primary.DigestAlgorithm.Value != KnownOids.SHA1)
21+
if (primary.SignerInfo.DigestAlgorithm.Value != KnownOids.SHA1)
2522
{
26-
verboseWriter.LogSignatureMessage(primary, $"Expected {nameof(KnownOids.SHA1)} digest algorithm but is {primary.DigestAlgorithm.FriendlyName}.");
23+
verboseWriter.LogSignatureMessage(primary.SignerInfo, $"Expected {nameof(KnownOids.SHA1)} digest algorithm but is {primary.SignerInfo.DigestAlgorithm.FriendlyName}.");
2724
return RuleResult.Fail;
2825
}
2926
return RuleResult.Pass;

AuthenticodeLint/Rules/Sha2SignatureExistsRule.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ public class Sha2SignatureExistsRule : IAuthenticodeRule
1313

1414
public string ShortDescription { get; } = "A SHA2 signature should exist.";
1515

16-
public RuleResult Validate(Graph<SignerInfo> graph, SignatureLoggerBase verboseWriter)
16+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
1717
{
1818
var signatures = graph.VisitAll();
1919
if (signatures.Any(s =>
20-
s.DigestAlgorithm.Value == KnownOids.SHA256 ||
21-
s.DigestAlgorithm.Value == KnownOids.SHA384 ||
22-
s.DigestAlgorithm.Value == KnownOids.SHA512))
20+
s.SignerInfo.DigestAlgorithm.Value == KnownOids.SHA256 ||
21+
s.SignerInfo.DigestAlgorithm.Value == KnownOids.SHA384 ||
22+
s.SignerInfo.DigestAlgorithm.Value == KnownOids.SHA512))
2323
{
2424
return RuleResult.Pass;
2525
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Security.Cryptography;
2+
using System.Security.Cryptography.Pkcs;
3+
using System.Security.Cryptography.X509Certificates;
4+
5+
namespace AuthenticodeLint.Rules
6+
{
7+
public class SigningCertificateDigestAlgorithmRule : IAuthenticodeRule
8+
{
9+
public int RuleId { get; } = 10006;
10+
11+
public string RuleName { get; } = "SHA2 Certificate Chain";
12+
13+
public string ShortDescription { get; } = "Checks the signing certificate's and chain's signature algorithm.";
14+
15+
public RuleResult Validate(Graph<Signature> graph, SignatureLoggerBase verboseWriter)
16+
{
17+
var signatures = graph.VisitAll();
18+
var result = RuleResult.Pass;
19+
foreach(var signature in signatures)
20+
{
21+
var certificates = signature.AdditionalCertificates;
22+
using (var chain = new X509Chain())
23+
{
24+
chain.ChainPolicy.ExtraStore.AddRange(certificates);
25+
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
26+
bool success = chain.Build(signature.SignerInfo.Certificate);
27+
if (!success)
28+
{
29+
verboseWriter.LogSignatureMessage(signature.SignerInfo, $"Cannot build a chain successfully with signing certificate {signature.SignerInfo.Certificate.SerialNumber}.");
30+
result = RuleResult.Fail;
31+
continue;
32+
}
33+
var chainResult = ValidateSha2Chain(signature.SignerInfo, chain, verboseWriter);
34+
if (!chainResult)
35+
{
36+
result = RuleResult.Fail;
37+
}
38+
}
39+
}
40+
return result;
41+
}
42+
43+
private static bool ValidateSha2Chain(SignerInfo signatureInfo, X509Chain chain, SignatureLoggerBase verboseWriter)
44+
{
45+
var strongSha2Chain = true;
46+
//We use count-1 because we don't want to validate SHA2 on the root certificate.
47+
for(var i = 0; i < chain.ChainElements.Count-1; i++)
48+
{
49+
var element = chain.ChainElements[i];
50+
var signatureAlgorithm = element.Certificate.SignatureAlgorithm;
51+
switch (signatureAlgorithm.Value)
52+
{
53+
case KnownOids.sha256ECDSA:
54+
case KnownOids.sha384ECDSA:
55+
case KnownOids.sha512ECDSA:
56+
case KnownOids.sha256RSA:
57+
case KnownOids.sha384RSA:
58+
case KnownOids.sha512RSA:
59+
continue;
60+
default:
61+
verboseWriter.LogSignatureMessage(signatureInfo, $"Certificate {element.Certificate.Thumbprint} in chain uses {element.Certificate.SignatureAlgorithm.FriendlyName} for its signature algorithm instead of SHA2.");
62+
strongSha2Chain = false;
63+
break;
64+
}
65+
}
66+
return strongSha2Chain;
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)