Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test | Unit Test for decrypt failure to drain data fix #2844

Merged
merged 12 commits into from
Oct 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void TestEncryptDecryptWithAKV()
AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified,
EnclaveAttestationUrl = ""
};
using SqlConnection sqlConnection = new (builder.ConnectionString);
using SqlConnection sqlConnection = new(builder.ConnectionString);

sqlConnection.Open();
Customer customer = new(45, "Microsoft", "Corporation");
Expand All @@ -48,7 +48,7 @@ public void TestEncryptDecryptWithAKV()
}

// Test INPUT parameter on an encrypted parameter
using SqlCommand sqlCommand = new ($"SELECT CustomerId, FirstName, LastName FROM [{_akvTableName}] WHERE FirstName = @firstName",
using SqlCommand sqlCommand = new($"SELECT CustomerId, FirstName, LastName FROM [{_akvTableName}] WHERE FirstName = @firstName",
sqlConnection);
SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft");
customerFirstParam.Direction = System.Data.ParameterDirection.Input;
Expand All @@ -58,11 +58,74 @@ public void TestEncryptDecryptWithAKV()
DatabaseHelper.ValidateResultSet(sqlDataReader);
}

/*
This unit test is designed for PR #2618, which addresses an issue where a failed decryption leaves a connection in a bad state
arellegue marked this conversation as resolved.
Show resolved Hide resolved
when it is returned to the connection pool. If a subsequent connection is retried it will result in an "Internal connection fatal error",
which causes that connection to be doomed, preventing it from being returned to the pool.
Consequently, retrying a new connection will encounter the same decryption error, leading to a repetitive failure cycle.

The purpose of this unit test is to simulate a decryption error and verify that the connection remains usable when returned to the pool.
It aims to confirm that three consecutive connections will consistently fail with the "Failed to decrypt column" error.
*/
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.IsAKVSetupAvailable))]
public void ForcedColumnDecryptErrorTestShouldFail()
{
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionStringHGSVBS)
{
ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled,
AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified,
EnclaveAttestationUrl = ""
};

// Setup record to query
using (SqlConnection sqlConnection = new(builder.ConnectionString))
{
sqlConnection.Open();
Customer customer = new(88, "Microsoft2", "Corporation2");

using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
{
DatabaseHelper.InsertCustomerData(sqlConnection, sqlTransaction, _akvTableName, customer);
sqlTransaction.Commit();
}
}

// Setup Empty key store provider
Dictionary<String, SqlColumnEncryptionKeyStoreProvider> emptyKeyStoreProviders = new()
{
{ "AZURE_KEY_VAULT", new EmptyKeyStoreProvider() }
};

// Three consecutive connections should fail with "Failed to decrypt column" error. This proves that an error in decryption
// does not leave the connection in a bad state.
for (int i = 0; i < 3; i++)
DavoudEshtehari marked this conversation as resolved.
Show resolved Hide resolved
{
using (SqlConnection sqlConnection = new SqlConnection(builder.ConnectionString))
{
sqlConnection.Open();
// Setup connection using the empty key store provider thereby forcing a decryption error.
sqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(emptyKeyStoreProviders);

using SqlCommand sqlCommand = new($"SELECT FirstName FROM [{_akvTableName}] WHERE FirstName = @firstName", sqlConnection);
SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft2");
customerFirstParam.Direction = System.Data.ParameterDirection.Input;
customerFirstParam.ForceColumnEncryption = true;

using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();
while (sqlDataReader.Read())
{
var error = Assert.Throws<SqlException>(() => DatabaseHelper.CompareResults(sqlDataReader, new string[] { @"string" }, 1));
Assert.Contains("Failed to decrypt column", error.Message);
}
}
}
}

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))]
[PlatformSpecific(TestPlatforms.Windows)]
public void TestRoundTripWithAKVAndCertStoreProvider()
{
using SQLSetupStrategyCertStoreProvider certStoreFixture = new ();
using SQLSetupStrategyCertStoreProvider certStoreFixture = new();
byte[] plainTextColumnEncryptionKey = ColumnEncryptionKey.GenerateRandomBytes(ColumnEncryptionKey.KeySizeInBytes);
byte[] encryptedColumnEncryptionKeyUsingAKV = _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, @"RSA_OAEP", plainTextColumnEncryptionKey);
byte[] columnEncryptionKeyReturnedAKV2Cert = certStoreFixture.CertStoreProvider.DecryptColumnEncryptionKey(certStoreFixture.CspColumnMasterKey.KeyPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingAKV);
Expand Down Expand Up @@ -120,5 +183,18 @@ public void TestLocalCekCacheIsScopedToProvider()
Exception ex = Assert.Throws<SqlException>(() => sqlCommand.ExecuteReader());
Assert.StartsWith("The current credential is not configured to acquire tokens for tenant", ex.InnerException.Message);
}

private class EmptyKeyStoreProvider : SqlColumnEncryptionKeyStoreProvider
{
public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
{
return new byte[32];
}

public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
{
return new byte[32];
}
}
}
}
Loading