Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#### For the official .NET Release Notes please refer to https://docs.snowflake.com/en/release-notes/clients-drivers/dotnet

# Changelog
- v5.1.1
- Fixed CRL validation to reject newly downloaded CRLs if their NextUpdate has already expired.
- v5.1.0
- Added `APPLICATION_PATH` to `CLIENT_ENVIRONMENT` sent during authentication to identify the application connecting to Snowflake.
- Renew idle sessions in the pool if keep alive is enabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ public void TestVerifyCertificateAsUnrevoked()
MockByteResponseForGet(restRequester, DigiCertCrlUrl2, crlBytes);
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
var environmentOperation = new Mock<EnvironmentOperations>();
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
var timeProvider = new Mock<TimeProvider>();
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);

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

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

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

var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
var environmentOperation = new Mock<EnvironmentOperations>();
var verifier = new CertificateRevocationVerifier(config, TimeProvider.Instance, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);
var timeProvider = new Mock<TimeProvider>();
var testTime = DateTimeOffset.Parse(DigiCertThisUpdateString).UtcDateTime.AddHours(1);
timeProvider.Setup(tp => tp.UtcNow()).Returns(testTime);
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);

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

[Test]
public void TestDownloadedCrlIsExpiredAndNoneValidExists()
{
// arrange
var now = DateTime.UtcNow;
var timeProvider = new Mock<TimeProvider>();
timeProvider.Setup(tp => tp.UtcNow()).Returns(now);
var expectedCrlUrls = new[] { "http://test.crl" };

var certKeys = CertificateGenerator.GenerateKeysForCertAndItsParent();
var certSubject = "CN=Test Cert CN, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
var rootSubject = "CN=Test Root CA, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
var certificate = CertificateGenerator.GenerateCertificate(certSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), new[] { expectedCrlUrls }, certKeys[0]);
var parentCertificate = CertificateGenerator.GenerateCertificate(rootSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), null, certKeys[1]);
var config = GetHttpConfig();

var expiredCrl = CertificateGenerator.GenerateCrl(
CertificateGenerator.SHA256WithRsaAlgorithm,
certKeys[1].Private,
rootSubject,
now.AddHours(-2),
now.AddHours(-1),
now.AddHours(-2)
);
var crlBytes = expiredCrl.GetEncoded();

var restRequester = new Mock<IRestRequester>();
MockByteResponseForGet(restRequester, expectedCrlUrls[0], crlBytes);
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
var environmentOperation = new Mock<EnvironmentOperations>();
var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, new CrlParser(environmentOperation.Object), crlRepository);

// act
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);

// assert
Assert.AreEqual(CertRevocationCheckResult.CertError, result);
}

[Test]
public void TestDownloadedCrlIsExpiredButTheValidExists()
{
// arrange
var now = DateTime.UtcNow;
var timeProvider = new Mock<TimeProvider>();
timeProvider.Setup(tp => tp.UtcNow()).Returns(now);
var expectedCrlUrls = new[] { "http://test.crl" };

var certKeys = CertificateGenerator.GenerateKeysForCertAndItsParent();
var certSubject = "CN=Test Cert CN, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
var rootSubject = "CN=Test Root CA, O=Snowflake, OU=Drivers, L=Warsaw, ST=Masovian, C=Poland";
var certificate = CertificateGenerator.GenerateCertificate(certSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), new[] { expectedCrlUrls }, certKeys[0]);
var parentCertificate = CertificateGenerator.GenerateCertificate(rootSubject, rootSubject, DateTimeOffset.Now.AddDays(-1), DateTimeOffset.Now.AddDays(300), null, certKeys[1]);
var config = GetHttpConfig();

var validCrl = CertificateGenerator.GenerateCrl(
CertificateGenerator.SHA256WithRsaAlgorithm,
certKeys[1].Private,
rootSubject,
now.AddDays(-2),
now.AddHours(2), // NextUpdate in future
now.AddDays(-2)
);

var expiredCrl = CertificateGenerator.GenerateCrl(
CertificateGenerator.SHA256WithRsaAlgorithm,
certKeys[1].Private,
rootSubject,
now.AddHours(-2),
now.AddHours(-1),
now.AddHours(-2)
);
var expiredCrlBytes = expiredCrl.GetEncoded();

var restRequester = new Mock<IRestRequester>();
MockByteResponseForGet(restRequester, expectedCrlUrls[0], expiredCrlBytes);
var crlRepository = new CrlRepository(config.EnableCRLInMemoryCaching, config.EnableCRLDiskCaching);
var environmentOperation = new Mock<EnvironmentOperations>();
var crlParser = new CrlParser(environmentOperation.Object);

// Pre-populate cache with valid CRL
var cachedCrlObject = crlParser.Create(validCrl, now.AddHours(-25));
crlRepository.Set(expectedCrlUrls[0], cachedCrlObject);

var verifier = new CertificateRevocationVerifier(config, timeProvider.Object, restRequester.Object, CertificateCrlDistributionPointsExtractor.Instance, crlParser, crlRepository);

// act
var result = verifier.CheckCertRevocation(certificate, expectedCrlUrls, parentCertificate);

// assert
Assert.AreEqual(CertRevocationCheckResult.CertUnrevoked, result);
}

private HttpClientConfig GetHttpConfig(CertRevocationCheckMode checkMode = CertRevocationCheckMode.Enabled, long crlDownloadMaxSize = 209715200) =>
new HttpClientConfig(
null,
Expand All @@ -353,7 +458,7 @@ private HttpClientConfig GetHttpConfig(CertRevocationCheckMode checkMode = CertR
true,
checkMode.ToString(),
false,
false,
true,
false,
10,
crlDownloadMaxSize);
Expand Down
12 changes: 12 additions & 0 deletions Snowflake.Data/Core/Revocation/CertificateRevocationVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,18 @@ private CertRevocationCheckResult CheckCertRevocationForOneCrlUrl(X509Certificat
if (needsFreshCrl)
{
var newCrl = FetchCrl(crlUrl);

if (newCrl != null && newCrl.NextUpdate.HasValue && newCrl.NextUpdate.Value < now)
{
s_logger.Warn($"Downloaded CRL from '{crlUrl}' is already expired (next update: {newCrl.NextUpdate.Value})");
newCrl = null;
if (cachedCrl == null)
{
s_logger.Error($"Unable to fetch a valid CRL from '{crlUrl}' for certificate: '{certificate.Subject}'. Downloaded CRL is expired and no fallback available.");
return CertRevocationCheckResult.CertError;
}
}

shouldUpdateCrl = newCrl != null && (cachedCrl == null || newCrl.ThisUpdate > cachedCrl.ThisUpdate);
if (shouldUpdateCrl)
{
Expand Down
Loading