20
20
using System . Net . Security ;
21
21
using System . Security . Cryptography ;
22
22
using System . Security . Cryptography . X509Certificates ;
23
- using System . Text . RegularExpressions ;
24
23
25
24
namespace Cassandra . DataStax . Cloud
26
25
{
@@ -32,9 +31,52 @@ internal class CustomCaCertificateValidator : ICertificateValidator
32
31
private const string SubjectAlternateNameOid = "2.5.29.17" ; // Oid for the SAN extension
33
32
34
33
private static readonly Logger Logger = new Logger ( typeof ( CustomCaCertificateValidator ) ) ;
34
+
35
+ private static readonly string SanPlatformId ;
36
+ private static readonly string SanSeparator ;
37
+
35
38
private readonly X509Certificate2 _trustedRootCertificateAuthority ;
36
39
private readonly string _hostname ;
37
40
41
+ static CustomCaCertificateValidator ( )
42
+ {
43
+ // use a well known example SAN extension to extract the platform identifier (since it is affected by platform and locale/culture)
44
+
45
+ const string wellKnownSanExtension = @"MBuCC2V4YW1wbGUuY29tggxmYWtlLXN1YmplY3Q=" ;
46
+ const string firstWellKnownDomainName = "example.com" ;
47
+ const string secondWellKnownDomainName = "fake-subject" ;
48
+
49
+ var formattedExtString = new X509Extension ( SubjectAlternateNameOid , Convert . FromBase64String ( wellKnownSanExtension ) , true ) . Format ( false ) ;
50
+
51
+ // Windows identifier is affected by locale/culture
52
+ // Example well known SAN extension has the following format:
53
+ // Windows: "DNS Name=example.com, DNS Name=fake-subject"
54
+ // Linux: "DNS:example.com, DNS:fake-subject"
55
+ //
56
+ // Parse it as the following:
57
+ // <platform-id><domain-name><separator><platform-id><domain-name>
58
+ // e.g. for Windows with EN culture:
59
+ // platform-id -> "DNS Name="
60
+ // first-domain-name -> "example.com"
61
+ // second-domain-name -> "fake-subject"
62
+ // separator -> ", "
63
+
64
+ // "example.com"
65
+ var firstDomainNameIndex = formattedExtString . IndexOf ( firstWellKnownDomainName , StringComparison . Ordinal ) ;
66
+
67
+ // "fake-subject"
68
+ var secondDomainNameIndex = formattedExtString . IndexOf ( secondWellKnownDomainName , StringComparison . Ordinal ) ;
69
+
70
+ // "DNS Name="
71
+ SanPlatformId = formattedExtString . Substring ( 0 , firstDomainNameIndex ) ;
72
+
73
+ // ", "
74
+ var separatorIndex = firstDomainNameIndex + firstWellKnownDomainName . Length ;
75
+ var lengthUntilSeparator = firstDomainNameIndex + firstWellKnownDomainName . Length ;
76
+ var separatorLength = secondDomainNameIndex - SanPlatformId . Length - lengthUntilSeparator ;
77
+ SanSeparator = formattedExtString . Substring ( separatorIndex , separatorLength ) ;
78
+ }
79
+
38
80
public CustomCaCertificateValidator ( X509Certificate2 trustedRootCertificateAuthority , string hostname )
39
81
{
40
82
_trustedRootCertificateAuthority =
@@ -168,32 +210,23 @@ private bool ValidateName(string name)
168
210
return false ;
169
211
}
170
212
213
+ /// <summary>
214
+ /// Check the static constructor inline comments for an explanation about this
215
+ /// </summary>
171
216
private IEnumerable < string > GetSubjectAlternativeNames ( X509Certificate2 cert )
172
217
{
173
- var result = new List < string > ( ) ;
174
-
175
- var subjectAlternativeName = cert . Extensions . Cast < X509Extension > ( )
176
- . Where ( n => n . Oid . Value == SubjectAlternateNameOid )
177
- . Select ( n => new AsnEncodedData ( n . Oid , n . RawData ) )
178
- . Select ( n => n . Format ( true ) )
179
- . FirstOrDefault ( ) ;
218
+ var sanStrings = cert . Extensions
219
+ . Cast < X509Extension > ( )
220
+ . Where ( ext => ext . Oid . Value == SubjectAlternateNameOid )
221
+ . Select ( ext => new AsnEncodedData ( ext . Oid , ext . RawData ) . Format ( false ) ) . ToList ( ) ;
180
222
181
- if ( subjectAlternativeName != null )
182
- {
183
- var alternativeNames = subjectAlternativeName . Split ( new [ ] { "\r \n " , "\r " , "\n " } , StringSplitOptions . None ) ;
223
+ var splitSanStrings = sanStrings . SelectMany ( s => s . Split ( new [ ] { SanSeparator } , StringSplitOptions . RemoveEmptyEntries ) ) ;
184
224
185
- foreach ( var alternativeName in alternativeNames )
186
- {
187
- var groups = Regex . Match ( alternativeName , @"^(.*)=(.*)" ) . Groups ; // @"^DNS Name=(.*)").Groups;
188
-
189
- if ( groups . Count > 0 && ! string . IsNullOrEmpty ( groups [ 2 ] . Value ) )
190
- {
191
- result . Add ( groups [ 2 ] . Value ) ;
192
- }
193
- }
194
- }
225
+ // remove the platform identifier (i.e. "DNS Name=") from the strings to get the domain names
226
+ return splitSanStrings
227
+ . Where ( s => s . StartsWith ( SanPlatformId ) && s . Length > SanPlatformId . Length )
228
+ . Select ( s => s . Substring ( SanPlatformId . Length ) ) ;
195
229
196
- return result ;
197
230
}
198
231
199
232
private void GetOrCreateCert2 ( ref X509Certificate2 cert2 , X509Certificate cert )
0 commit comments