Skip to content

Commit 48c1254

Browse files
committed
Merge branch 'master' into mgemra/SNOW-2216772-alter-session-timezone
2 parents 0363612 + dfc1695 commit 48c1254

File tree

3 files changed

+124
-5
lines changed

3 files changed

+124
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#### For the official .NET Release Notes please refer to https://docs.snowflake.com/en/release-notes/clients-drivers/dotnet
22

33
# Changelog
4+
- v5.1.1
5+
- Fixed CRL validation to reject newly downloaded CRLs if their NextUpdate has already expired.
46
- v5.1.0
57
- Added `APPLICATION_PATH` to `CLIENT_ENVIRONMENT` sent during authentication to identify the application connecting to Snowflake.
68
- Renew idle sessions in the pool if keep alive is enabled.

Snowflake.Data.Tests/UnitTests/Revocation/CertificateRevocationVerifierTest.cs

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ public void TestVerifyCertificateAsUnrevoked()
5858
MockByteResponseForGet(restRequester, DigiCertCrlUrl2, crlBytes);
5959
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
6060
var environmentOperation = new Mock<EnvironmentOperations>();
61-
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
61+
var timeProvider = new Mock<TimeProvider>();
62+
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
63+
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
64+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
6265

6366
// act
6467
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
@@ -81,7 +84,10 @@ public void TestVerifyCertificateAsErrorWhenCouldNotDownloadCrl()
8184
MockErrorResponseForGet(restRequester, DigiCertCrlUrl2, NotFoundHttpExceptionProvider);
8285
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
8386
var environmentOperation = new Mock<EnvironmentOperations>();
84-
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
87+
var timeProvider = new Mock<TimeProvider>();
88+
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
89+
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
90+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
8591

8692
// act
8793
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
@@ -105,7 +111,10 @@ public void TestVerifyCertificateAsErrorWhenOneOfCrlsIsNotParsable()
105111
MockByteResponseForGet(restRequester, DigiCertCrlUrl1, notParsableCrlBytes);
106112
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
107113
var environmentOperation = new Mock<EnvironmentOperations>();
108-
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
114+
var timeProvider = new Mock<TimeProvider>();
115+
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
116+
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
117+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
109118

110119
// act
111120
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
@@ -141,7 +150,10 @@ public void TestVerifyCertificateAsErrorWhenCrlExceedsMaxSize()
141150

142151
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
143152
var environmentOperation = new Mock<EnvironmentOperations>();
144-
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
153+
var timeProvider = new Mock<TimeProvider>();
154+
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
155+
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
156+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
145157

146158
// act
147159
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
@@ -339,6 +351,99 @@ private static void MockErrorResponseForGet(Mock<IRestRequester> restRequester,
339351
.Throws(exceptionProvider);
340352
}
341353

354+
[Test]
355+
public void TestDownloadedCrlIsExpiredAndNoneValidExists()
356+
{
357+
// arrange
358+
var now = DateTime.UtcNow;
359+
var timeProvider = new Mock<TimeProvider>();
360+
timeProvider.Setup(tp => tp.UtcNow()).Returns(now);
361+
var expectedCrlUrls = new[] { "http://test.crl" };
362+
363+
var certKeys = CertificateGenerator.GenerateKeysForCertAndItsParent();
364+
var certSubject = "CN=Test Cert CN, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
365+
var rootSubject = "CN=Test Root CA, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
366+
var certificate = CertificateGenerator.GenerateCertificate(certSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), new[] { expectedCrlUrls }, certKeys[0]);
367+
var parentCertificate = CertificateGenerator.GenerateCertificate(rootSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), null, certKeys[1]);
368+
var config = GetHttpConfig();
369+
370+
var expiredCrl = CertificateGenerator.GenerateCrl(
371+
CertificateGenerator.SHA256WithRsaAlgorithm,
372+
certKeys[1].Private,
373+
rootSubject,
374+
now.AddHours(-2),
375+
now.AddHours(-1),
376+
now.AddHours(-2)
377+
);
378+
var crlBytes = expiredCrl.GetEncoded();
379+
380+
var restRequester = new Mock<IRestRequester>();
381+
MockByteResponseForGet(restRequester, expectedCrlUrls[0], crlBytes);
382+
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
383+
var environmentOperation = new Mock<EnvironmentOperations>();
384+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
385+
386+
// act
387+
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
388+
389+
// assert
390+
Assert.AreEqual(CertRevocationCheckResult.CertError, result);
391+
}
392+
393+
[Test]
394+
public void TestDownloadedCrlIsExpiredButTheValidExists()
395+
{
396+
// arrange
397+
var now = DateTime.UtcNow;
398+
var timeProvider = new Mock<TimeProvider>();
399+
timeProvider.Setup(tp => tp.UtcNow()).Returns(now);
400+
var expectedCrlUrls = new[] { "http://test.crl" };
401+
402+
var certKeys = CertificateGenerator.GenerateKeysForCertAndItsParent();
403+
var certSubject = "CN=Test Cert CN, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
404+
var rootSubject = "CN=Test Root CA, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
405+
var certificate = CertificateGenerator.GenerateCertificate(certSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), new[] { expectedCrlUrls }, certKeys[0]);
406+
var parentCertificate = CertificateGenerator.GenerateCertificate(rootSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), null, certKeys[1]);
407+
var config = GetHttpConfig();
408+
409+
var validCrl = CertificateGenerator.GenerateCrl(
410+
CertificateGenerator.SHA256WithRsaAlgorithm,
411+
certKeys[1].Private,
412+
rootSubject,
413+
now.AddDays(-2),
414+
now.AddHours(2), // NextUpdate in future
415+
now.AddDays(-2)
416+
);
417+
418+
var expiredCrl = CertificateGenerator.GenerateCrl(
419+
CertificateGenerator.SHA256WithRsaAlgorithm,
420+
certKeys[1].Private,
421+
rootSubject,
422+
now.AddHours(-2),
423+
now.AddHours(-1),
424+
now.AddHours(-2)
425+
);
426+
var expiredCrlBytes = expiredCrl.GetEncoded();
427+
428+
var restRequester = new Mock<IRestRequester>();
429+
MockByteResponseForGet(restRequester, expectedCrlUrls[0], expiredCrlBytes);
430+
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
431+
var environmentOperation = new Mock<EnvironmentOperations>();
432+
var crlParser = new CrlParser(environmentOperation.Object);
433+
434+
// Pre-populate cache with valid CRL
435+
var cachedCrlObject = crlParser.Create(validCrl, now.AddHours(-25));
436+
crlRepository.Set(expectedCrlUrls[0], cachedCrlObject);
437+
438+
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, crlParser, crlRepository);
439+
440+
// act
441+
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);
442+
443+
// assert
444+
Assert.AreEqual(CertRevocationCheckResult.CertUnrevoked, result);
445+
}
446+
342447
private HttpClientConfig GetHttpConfig(CertRevocationCheckMode checkMode = CertRevocationCheckMode.Enabled, long crlDownloadMaxSize = 209715200) =>
343448
new HttpClientConfig(
344449
null,
@@ -353,7 +458,7 @@ private HttpClientConfig GetHttpConfig(CertRevocationCheckMode checkMode = CertR
353458
true,
354459
checkMode.ToString(),
355460
false,
356-
false,
461+
true,
357462
false,
358463
10,
359464
crlDownloadMaxSize);

Snowflake.Data/Core/Revocation/CertificateRevocationVerifier.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,18 @@ private CertRevocationCheckResult CheckCertRevocationForOneCrlUrl(X509Certificat
198198
if (needsFreshCrl)
199199
{
200200
var newCrl = FetchCrl(crlUrl);
201+
202+
if (newCrl != null && newCrl.NextUpdate.HasValue && newCrl.NextUpdate.Value < now)
203+
{
204+
s_logger.Warn($"Downloaded CRL from '{crlUrl}' is already expired (next update: {newCrl.NextUpdate.Value})");
205+
newCrl = null;
206+
if (cachedCrl == null)
207+
{
208+
s_logger.Error($"Unable to fetch a valid CRL from '{crlUrl}' for certificate: '{certificate.Subject}'. Downloaded CRL is expired and no fallback available.");
209+
return CertRevocationCheckResult.CertError;
210+
}
211+
}
212+
201213
shouldUpdateCrl = newCrl != null && (cachedCrl == null || newCrl.ThisUpdate > cachedCrl.ThisUpdate);
202214
if (shouldUpdateCrl)
203215
{

0 commit comments

Comments
 (0)