Skip to content

Commit 886baa2

Browse files
authored
Merge pull request #178 from bbarry/master
improve x509 related code
2 parents a9d77ee + 76ea503 commit 886baa2

File tree

2 files changed

+66
-40
lines changed

2 files changed

+66
-40
lines changed

src/AdoNetCore.AseClient/Internal/InternalConnectionFactory.cs

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using System.Net;
@@ -190,20 +191,45 @@ private bool UserCertificateValidationCallback(object sender, X509Certificate se
190191
return false;
191192
}
192193

193-
var untrustedRootChainStatusFlags = false;
194-
var otherChainStatusFlags = false;
195-
196-
// We're not concerned with UntrustedRoot errors as we verify that below.
194+
var mergedStatusFlags = X509ChainStatusFlags.NoError;
197195
foreach (var status in chain.ChainStatus)
198196
{
199-
untrustedRootChainStatusFlags |= (status.Status & X509ChainStatusFlags.UntrustedRoot) == X509ChainStatusFlags.UntrustedRoot;
200-
otherChainStatusFlags |= (status.Status & ~X509ChainStatusFlags.UntrustedRoot) != X509ChainStatusFlags.NoError;
197+
mergedStatusFlags |= status.Status;
198+
}
199+
200+
var trustedCerts = LoadTrustedFile(_parameters.TrustedFile);
201+
if (trustedCerts == null)
202+
{
203+
Logger.Instance?.WriteLine($"{nameof(InternalConnectionFactory)}.{nameof(UserCertificateValidationCallback)} secure connection failed due to missing TrustedFile parameter.");
204+
return false;
205+
}
201206

202-
if (otherChainStatusFlags)
207+
#if !(NETCOREAPP1_0 || NETCOREAPP1_1) // these frameworks do not have the following X509Certificate2 constructor...
208+
// sometimes the chain policy is only a partial chain because it doesn't include a self signed root?
209+
if ((mergedStatusFlags & X509ChainStatusFlags.PartialChain) == X509ChainStatusFlags.PartialChain)
210+
{
211+
// attempt to resolve a partial root by rebuilding the cert chain including the certs from the trusted file
212+
chain.ChainPolicy.ExtraStore.AddRange(trustedCerts);
213+
if (chain.Build(new X509Certificate2(serverCertificate)))
203214
{
204-
Logger.Instance?.WriteLine($"{nameof(InternalConnectionFactory)}.{nameof(UserCertificateValidationCallback)} secure connection failed due to chain status: {status.Status}");
205-
return false;
215+
// Chain validated with extra roots added; accept it
216+
return true;
206217
}
218+
mergedStatusFlags = X509ChainStatusFlags.NoError;
219+
foreach (var status in chain.ChainStatus)
220+
{
221+
mergedStatusFlags |= status.Status;
222+
}
223+
}
224+
#endif
225+
226+
var untrustedRootChainStatusFlags = (mergedStatusFlags & X509ChainStatusFlags.UntrustedRoot) == X509ChainStatusFlags.UntrustedRoot;
227+
var otherChainStatusFlags = (mergedStatusFlags & ~X509ChainStatusFlags.UntrustedRoot) != X509ChainStatusFlags.NoError;
228+
229+
if (otherChainStatusFlags)
230+
{
231+
Logger.Instance?.WriteLine($"{nameof(InternalConnectionFactory)}.{nameof(UserCertificateValidationCallback)} secure connection failed due to chain status: {mergedStatusFlags}");
232+
return false;
207233
}
208234

209235
if (!(certificateChainPolicyErrors || untrustedRootChainStatusFlags))
@@ -212,35 +238,42 @@ private bool UserCertificateValidationCallback(object sender, X509Certificate se
212238
return true;
213239
}
214240

241+
// If any certificates in the chain are trusted, then we will trust the server certificate.
242+
// To do this fairly quickly we can check if thumbprints exist in the set of trusted roots.
243+
var set = new HashSet<string>(trustedCerts.Select(c => c.Thumbprint));
244+
245+
// the chain is in an array from leaf at 0 to root at [count - 1]
246+
// looping from end to start should find cases generated according to sybase documentation on the first attempt
247+
// but it is possible that someone puts an intermediate or even the leaf cert in their trusted file
248+
for (int i = chain.ChainElements.Count - 1; i >= 0; i--)
249+
{
250+
var potentialTrusted = chain.ChainElements[i].Certificate.Thumbprint;
251+
if (set.Contains(potentialTrusted))
252+
{
253+
return true;
254+
}
255+
}
256+
257+
Logger.Instance?.WriteLine($"{nameof(InternalConnectionFactory)}.{nameof(UserCertificateValidationCallback)} secure connection failed due to missing root or intermediate certificate in the certificate store, or the TrustedFile.");
258+
259+
return false;
260+
}
261+
262+
private static X509Certificate2[] LoadTrustedFile(string trustedFile)
263+
{
215264
// The TrustedFile is a file containing the public keys, in PEM format of the trusted
216265
// root certificates that this client is willing to accept TLS connections from.
217-
if (!string.IsNullOrWhiteSpace(_parameters.TrustedFile) && File.Exists(_parameters.TrustedFile))
266+
if (!string.IsNullOrWhiteSpace(trustedFile) && File.Exists(trustedFile))
218267
{
219268
try
220269
{
221-
var trustedRootCertificatesPem = File.ReadAllText(_parameters.TrustedFile, Encoding.ASCII);
270+
var trustedRootCertificatesPem = File.ReadAllText(trustedFile, Encoding.ASCII);
222271

223272
var parser = new PemParser();
224273
var rootCertificates =
225-
parser.ParseCertificates(trustedRootCertificatesPem)
226-
.ToDictionary(GetCertificateKey);
227-
228-
// If the server certificate itself is the root, weird, but ok.
229-
if (rootCertificates.ContainsKey(GetCertificateKey(serverCertificate)))
230-
{
231-
return true;
232-
}
274+
parser.ParseCertificates(trustedRootCertificatesPem).ToArray();
233275

234-
// If any certificates in the chain are trusted, then we will trust the server certificate.
235-
foreach (var chainElement in chain.ChainElements)
236-
{
237-
var key = GetCertificateKey(chainElement.Certificate);
238-
239-
if (rootCertificates.ContainsKey(key))
240-
{
241-
return true;
242-
}
243-
}
276+
return rootCertificates;
244277
}
245278
catch (CryptographicException e)
246279
{
@@ -252,14 +285,7 @@ private bool UserCertificateValidationCallback(object sender, X509Certificate se
252285
}
253286
}
254287

255-
Logger.Instance?.WriteLine($"{nameof(InternalConnectionFactory)}.{nameof(UserCertificateValidationCallback)} secure connection failed due to missing root or intermediate certificate in the certificate store, or the TrustedFile.");
256-
257-
return false;
258-
}
259-
260-
private static string GetCertificateKey(X509Certificate certificate)
261-
{
262-
return $"{certificate.Issuer}|{Convert.ToBase64String(certificate.GetSerialNumber())}";
288+
return null;
263289
}
264290

265291
private static IPEndPoint CreateEndpoint(string server, int port, CancellationToken token)

src/AdoNetCore.AseClient/Internal/PemParser.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal sealed class PemParser
1111
"(?<certificate>(-----BEGIN CERTIFICATE-----)(.|\r\n|\r|\n)+?(-----END CERTIFICATE-----))",
1212
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline);
1313

14-
public IEnumerable<X509Certificate> ParseCertificates(string data)
14+
public IEnumerable<X509Certificate2> ParseCertificates(string data)
1515
{
1616
var matches = CertificateRegex.Matches(data);
1717

@@ -24,9 +24,9 @@ public IEnumerable<X509Certificate> ParseCertificates(string data)
2424
}
2525
}
2626
}
27-
public X509Certificate ParseCertificate(string data)
27+
public X509Certificate2 ParseCertificate(string data)
2828
{
29-
return new X509Certificate(Encoding.ASCII.GetBytes(data));
29+
return new X509Certificate2(Encoding.ASCII.GetBytes(data));
3030
}
3131
}
3232
}

0 commit comments

Comments
 (0)