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

Feature | AKV Provider Upgrade to use new Azure key Vault libraries #630

Merged
merged 25 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
185fd6f
AKV Upgrade
cheenamalhotra Jun 29, 2020
5b637b7
Add net461 support, drops net46 support.
cheenamalhotra Jun 30, 2020
7c0ecde
Adjust tests for AKV Upgrade
cheenamalhotra Jul 6, 2020
6d7eb45
Apply feedback comments
cheenamalhotra Jul 8, 2020
8b6d6aa
Address review feedback + remove old constructors.
cheenamalhotra Jul 20, 2020
47f4a0f
Update design of AKV Provider with multi-user support example
cheenamalhotra Aug 7, 2020
8c9fefc
Minor edit
cheenamalhotra Aug 7, 2020
33ab71d
Merge branch 'master' into akv-upgrade
cheenamalhotra Aug 26, 2020
aea83c6
Merge branch 'master' into akv-upgrade
cheenamalhotra Aug 31, 2020
65d4696
fix key version bug
johnnypham Feb 2, 2021
0c6c2f5
add enclave example using new akv. update public api comments
johnnypham Feb 5, 2021
52dc29d
Merge pull request #3 from johnnypham/akv-upgrade
cheenamalhotra Feb 5, 2021
0049b64
update failing tests
johnnypham Feb 8, 2021
d8278f8
update tests
johnnypham Feb 8, 2021
e54c463
Merge pull request #4 from johnnypham/akv-upgrade
cheenamalhotra Feb 9, 2021
6d7caa3
Merge branch 'akv-upgrade' of https://github.com/cheenamalhotra/SqlCl…
johnnypham Feb 10, 2021
cf8e1f7
resolve merge conflicts
johnnypham Feb 11, 2021
471d9e8
resolve merge conflicts
johnnypham Feb 11, 2021
fc20e0c
Merge pull request #5 from johnnypham/akv-upgrade
cheenamalhotra Feb 11, 2021
56e2aa0
Merge branch 'master' of https://github.com/dotnet/SqlClient into akv…
cheenamalhotra Feb 23, 2021
47cbaf6
Touch-ups and cleanups
cheenamalhotra Feb 23, 2021
ffa98ad
Formatting fix
cheenamalhotra Feb 23, 2021
68853b8
Apply suggestions from code review
cheenamalhotra Feb 26, 2021
405e564
Apply Suggestions from Code Review
cheenamalhotra Feb 26, 2021
34af2f7
Merge branch 'akv-upgrade' of https://github.com/cheenamalhotra/SqlCl…
cheenamalhotra Feb 26, 2021
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
1 change: 1 addition & 0 deletions BUILDGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Manual Tests require the below setup to run:
|AADSecurePrincipalId | (Optional) The Application Id of a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Application ID} |
|AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} |
|AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` |
|AzureKeyVaultTenantId | (Optional) The Azure Active Directory tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ |
|AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ |
|AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ |
|SupportsLocalDb | (Optional) Whether or not a LocalDb instance of SQL Server is installed on the machine running the tests. |`true` OR `false`|
Expand Down
248 changes: 248 additions & 0 deletions doc/samples/AzureKeyVaultProviderExample_2_0.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
//<Snippet1>
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Azure.Identity;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;

namespace Microsoft.Data.SqlClient.Samples
{
public class AzureKeyVaultProviderExample_2_0
{
static readonly string s_algorithm = "RSA_OAEP";

// ********* Provide details here ***********
static readonly string s_akvUrl = "https://{KeyVaultName}.vault.azure.net/keys/{Key}/{KeyIdentifier}";
static readonly string s_clientId = "{Application_Client_ID}";
static readonly string s_clientSecret = "{Application_Client_Secret}";
static readonly string s_tenantId = "{Azure_Key_Vault_Active_Directory_Tenant_Id}";
static readonly string s_connectionString = "Server={Server}; Database={database}; Integrated Security=true; Column Encryption Setting=Enabled;";
// ******************************************

public static void Main(string[] args)
{
// Initialize AKV provider
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(s_tenantId, s_clientId, s_clientSecret);
SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(clientSecretCredential);

// Register AKV provider
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
{
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, akvProvider}
});
Console.WriteLine("AKV provider Registered");

// Create connection to database
using (SqlConnection sqlConnection = new SqlConnection(s_connectionString))
{
string cmkName = "CMK_WITH_AKV";
string cekName = "CEK_WITH_AKV";
string tblName = "AKV_TEST_TABLE";

CustomerRecord customer = new CustomerRecord(1, @"Microsoft", @"Corporation");

try
{
sqlConnection.Open();

// Drop Objects if exists
dropObjects(sqlConnection, cmkName, cekName, tblName);

// Create Column Master Key with AKV Url
createCMK(sqlConnection, cmkName);
Console.WriteLine("Column Master Key created.");

// Create Column Encryption Key
createCEK(sqlConnection, cmkName, cekName, akvProvider);
Console.WriteLine("Column Encryption Key created.");

// Create Table with Encrypted Columns
createTbl(sqlConnection, cekName, tblName);
Console.WriteLine("Table created with Encrypted columns.");

// Insert Customer Record in table
insertData(sqlConnection, tblName, customer);
Console.WriteLine("Encryted data inserted.");

// Read data from table
verifyData(sqlConnection, tblName, customer);
Console.WriteLine("Data validated successfully.");
}
finally
{
// Drop table and keys
dropObjects(sqlConnection, cmkName, cekName, tblName);
Console.WriteLine("Dropped Table, CEK and CMK");
}

Console.WriteLine("Completed AKV provider Sample.");
}
}

private static void createCMK(SqlConnection sqlConnection, string cmkName)
{
string KeyStoreProviderName = SqlColumnEncryptionAzureKeyVaultProvider.ProviderName;

string sql =
$@"CREATE COLUMN MASTER KEY [{cmkName}]
WITH (
KEY_STORE_PROVIDER_NAME = N'{KeyStoreProviderName}',
KEY_PATH = N'{s_akvUrl}'
);";

using (SqlCommand command = sqlConnection.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}

private static void createCEK(SqlConnection sqlConnection, string cmkName, string cekName, SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider)
{
string sql =
$@"CREATE COLUMN ENCRYPTION KEY [{cekName}]
WITH VALUES (
COLUMN_MASTER_KEY = [{cmkName}],
ALGORITHM = '{s_algorithm}',
ENCRYPTED_VALUE = {GetEncryptedValue(sqlColumnEncryptionAzureKeyVaultProvider)}
)";

using (SqlCommand command = sqlConnection.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}

private static string GetEncryptedValue(SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider)
{
byte[] plainTextColumnEncryptionKey = new byte[32];
RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
rngCsp.GetBytes(plainTextColumnEncryptionKey);

byte[] encryptedColumnEncryptionKey = sqlColumnEncryptionAzureKeyVaultProvider.EncryptColumnEncryptionKey(s_akvUrl, s_algorithm, plainTextColumnEncryptionKey);
string EncryptedValue = string.Concat("0x", BitConverter.ToString(encryptedColumnEncryptionKey).Replace("-", string.Empty));
return EncryptedValue;
}

private static void createTbl(SqlConnection sqlConnection, string cekName, string tblName)
{
string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256";

string sql =
$@"CREATE TABLE [dbo].[{tblName}]
(
[CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
[LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}')
)";

using (SqlCommand command = sqlConnection.CreateCommand())
{
command.CommandText = sql;
command.ExecuteNonQuery();
}
}

private static void insertData(SqlConnection sqlConnection, string tblName, CustomerRecord customer)
{
string insertSql = $"INSERT INTO [{tblName}] (CustomerId, FirstName, LastName) VALUES (@CustomerId, @FirstName, @LastName);";

using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
using (SqlCommand sqlCommand = new SqlCommand(insertSql,
connection: sqlConnection, transaction: sqlTransaction,
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled))
{
sqlCommand.Parameters.AddWithValue(@"CustomerId", customer.Id);
sqlCommand.Parameters.AddWithValue(@"FirstName", customer.FirstName);
sqlCommand.Parameters.AddWithValue(@"LastName", customer.LastName);

sqlCommand.ExecuteNonQuery();
sqlTransaction.Commit();
}
}

private static void verifyData(SqlConnection sqlConnection, string tblName, CustomerRecord customer)
{
// Test INPUT parameter on an encrypted parameter
using (SqlCommand sqlCommand = new SqlCommand($"SELECT CustomerId, FirstName, LastName FROM [{tblName}] WHERE FirstName = @firstName",
sqlConnection))
{
SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft");
customerFirstParam.Direction = System.Data.ParameterDirection.Input;
customerFirstParam.ForceColumnEncryption = true;

using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
ValidateResultSet(sqlDataReader);
}
}
}

private static void ValidateResultSet(SqlDataReader sqlDataReader)
{
Console.WriteLine(" * Row available: " + sqlDataReader.HasRows);

while (sqlDataReader.Read())
{
if (sqlDataReader.GetInt32(0) == 1)
{
Console.WriteLine(" * Employee Id received as sent: " + sqlDataReader.GetInt32(0));
}
else
{
Console.WriteLine("Employee Id didn't match");
}

if (sqlDataReader.GetString(1) == @"Microsoft")
{
Console.WriteLine(" * Employee Firstname received as sent: " + sqlDataReader.GetString(1));
}
else
{
Console.WriteLine("Employee FirstName didn't match.");
}

if (sqlDataReader.GetString(2) == @"Corporation")
{
Console.WriteLine(" * Employee LastName received as sent: " + sqlDataReader.GetString(2));
}
else
{
Console.WriteLine("Employee LastName didn't match.");
}
}
}

private static void dropObjects(SqlConnection sqlConnection, string cmkName, string cekName, string tblName)
{
using (SqlCommand cmd = sqlConnection.CreateCommand())
{
cmd.CommandText = $@"IF EXISTS (select * from sys.objects where name = '{tblName}') BEGIN DROP TABLE [{tblName}] END";
cmd.ExecuteNonQuery();
cmd.CommandText = $@"IF EXISTS (select * from sys.column_encryption_keys where name = '{cekName}') BEGIN DROP COLUMN ENCRYPTION KEY [{cekName}] END";
cmd.ExecuteNonQuery();
cmd.CommandText = $@"IF EXISTS (select * from sys.column_master_keys where name = '{cmkName}') BEGIN DROP COLUMN MASTER KEY [{cmkName}] END";
cmd.ExecuteNonQuery();
}
}

private class CustomerRecord
{
internal int Id { get; set; }
internal string FirstName { get; set; }
internal string LastName { get; set; }

public CustomerRecord(int id, string fName, string lName)
{
Id = id;
FirstName = fName;
LastName = lName;
}
}
}

//</Snippet1>

}
Loading