-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCertHelpers.cs
151 lines (128 loc) · 4.11 KB
/
CertHelpers.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
namespace Aerospike.Database.LINQPadDriver
{
public static partial class CertHelpers
{
public enum ResultCodes
{
Unknown = 0,
Success,
NotFound,
Expired,
Premature,
NoTLSCommonName,
WrongTLSCommonName,
InvalidChain
}
public static X509Certificate2 LoadCertificateFromFile(string certificatePath)
{
byte[] derData;
//if(certificatePath.EndsWith(".pem"))
//{
// var pemData = File.ReadAllBytes(certificatePath);
// derData = ConvertPEMtoDER(pemData);
//}
//else
//{
// Already using a DER-formatted certificate
derData = File.ReadAllBytes(certificatePath);
//}
return new X509Certificate2(derData);
}
public static (ResultCodes, string) Validate(string certificatePath, IEnumerable<string> caAndChainPaths = null)
{
if(string.IsNullOrEmpty(certificatePath)
|| !File.Exists(certificatePath))
{
return (ResultCodes.NotFound, null);
}
using var certificateUnderValidation = LoadCertificateFromFile(certificatePath);
return Validate(certificateUnderValidation, caAndChainPaths);
}
public static (ResultCodes, string) Validate(X509Certificate2 certificateUnderValidation, IEnumerable<string> caAndChainPaths = null)
{
if(certificateUnderValidation is null)
{
return (ResultCodes.NotFound, null);
}
var subject = certificateUnderValidation.Subject;
if(string.IsNullOrEmpty(subject))
{
return (ResultCodes.NoTLSCommonName, null);
}
using var chain = new X509Chain();
if(caAndChainPaths is not null && caAndChainPaths.Any())
{
// .NET 5+ has a new 'CustomTrustStore' mode that permits ignoring the OS trust
// and ExtraTrust stores, and explicitly verify against an expected root CA (and
// its chain). This avoids the PartialChain issues in .NET Core 3 arising from
// the use of AllowUnknownCertificateAuthority, and allows us to trust the
// X509Chain.Build() verification result without extra steps.
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy
.CustomTrustStore
.AddRange(new X509Certificate2Collection(caAndChainPaths
.Select(file =>
LoadCertificateFromFile(file))
.ToArray()));
}
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
if(DateTime.Now <= certificateUnderValidation.NotBefore)
{
return (ResultCodes.Premature, subject);
}
if(certificateUnderValidation.NotAfter < DateTime.Now)
{
return (ResultCodes.Expired, subject);
}
return chain.Build(certificateUnderValidation)
? (ResultCodes.Success, subject)
: (ResultCodes.InvalidChain, subject);
}
public static (ResultCodes, string) Validate(X509CertificateCollection certificates, IEnumerable<string> caAndChainPaths = null)
{
if(certificates is null)
{
return (ResultCodes.NotFound, null);
}
string lastSubject = null;
foreach(var cert in certificates.Cast<X509Certificate2>())
{
var result = Validate(cert, caAndChainPaths);
if(result.Item1 != ResultCodes.Success)
{
return result;
}
lastSubject = result.Item2;
}
return (ResultCodes.Success, lastSubject);
}
const string CertSubjectStr = @"CN=(?<issueto>[^, ]+),?\s*";
#if NET7_0_OR_GREATER
//CN=tls1, O=Aerolab, S=CA, C=US
[GeneratedRegex(CertSubjectStr,
RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex CertSubjectRegEx();
#else
readonly static Regex CertSubjectRegExVar = new Regex(CertSubjectStr,
RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex CertSubjectRegEx() => CertSubjectRegExVar;
#endif
public static string ToIssuer(string subject)
{
if(string.IsNullOrEmpty(subject))
return null;
var match = CertSubjectRegEx().Match(subject);
if(match.Success)
return match.Groups["issueto"].Value;
return null;
}
}
}