Skip to content

Commit 4a5e510

Browse files
Add Release Notes for Azure Key Vault provider 1.0 (#278)
1 parent 4bbd42e commit 4a5e510

4 files changed

Lines changed: 306 additions & 0 deletions

File tree

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Security.Cryptography;
4+
using System.Threading.Tasks;
5+
using Microsoft.Data.SqlClient;
6+
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
7+
using Microsoft.IdentityModel.Clients.ActiveDirectory;
8+
9+
namespace Microsoft.Data.SqlClient.Samples
10+
{
11+
public class AzureKeyVaultProviderExample
12+
{
13+
static readonly string s_algorithm = "RSA_OAEP";
14+
15+
// ********* Provide details here ***********
16+
static readonly string s_akvUrl = "https://{KeyVaultName}.vault.azure.net/keys/{Key}/{KeyIdentifier}";
17+
static readonly string s_clientId = "{Application_Client_ID}";
18+
static readonly string s_clientSecret = "{Application_Client_Secret}";
19+
static readonly string s_connectionString = "Server={Server}; Database={database}; Integrated Security=true; Column Encryption Setting=Enabled;";
20+
// ******************************************
21+
22+
public static void Main(string[] args)
23+
{
24+
// Initialize AKV provider
25+
SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(AzureActiveDirectoryAuthenticationCallback);
26+
27+
// Register AKV provider
28+
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders: new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>(capacity: 1, comparer: StringComparer.OrdinalIgnoreCase)
29+
{
30+
{ SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, sqlColumnEncryptionAzureKeyVaultProvider}
31+
});
32+
Console.WriteLine("AKV provider Registered");
33+
34+
// Create connection to database
35+
using (SqlConnection sqlConnection = new SqlConnection(s_connectionString))
36+
{
37+
string cmkName = "CMK_WITH_AKV";
38+
string cekName = "CEK_WITH_AKV";
39+
string tblName = "AKV_TEST_TABLE";
40+
41+
CustomerRecord customer = new CustomerRecord(1, @"Microsoft", @"Corporation");
42+
43+
try
44+
{
45+
sqlConnection.Open();
46+
47+
// Drop Objects if exists
48+
dropObjects(sqlConnection, cmkName, cekName, tblName);
49+
50+
// Create Column Master Key with AKV Url
51+
createCMK(sqlConnection, cmkName);
52+
Console.WriteLine("Column Master Key created.");
53+
54+
// Create Column Encryption Key
55+
createCEK(sqlConnection, cmkName, cekName, sqlColumnEncryptionAzureKeyVaultProvider);
56+
Console.WriteLine("Column Encryption Key created.");
57+
58+
// Create Table with Encrypted Columns
59+
createTbl(sqlConnection, cekName, tblName);
60+
Console.WriteLine("Table created with Encrypted columns.");
61+
62+
// Insert Customer Record in table
63+
insertData(sqlConnection, tblName, customer);
64+
Console.WriteLine("Encryted data inserted.");
65+
66+
// Read data from table
67+
verifyData(sqlConnection, tblName, customer);
68+
Console.WriteLine("Data validated successfully.");
69+
}
70+
finally
71+
{
72+
// Drop table and keys
73+
dropObjects(sqlConnection, cmkName, cekName, tblName);
74+
Console.WriteLine("Dropped Table, CEK and CMK");
75+
}
76+
77+
Console.WriteLine("Completed AKV provider Sample.");
78+
}
79+
}
80+
81+
public static async Task<string> AzureActiveDirectoryAuthenticationCallback(string authority, string resource, string scope)
82+
{
83+
var authContext = new AuthenticationContext(authority);
84+
ClientCredential clientCred = new ClientCredential(s_clientId, s_clientSecret);
85+
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
86+
if (result == null)
87+
{
88+
throw new InvalidOperationException($"Failed to retrieve an access token for {resource}");
89+
}
90+
91+
return result.AccessToken;
92+
}
93+
94+
private static void createCMK(SqlConnection sqlConnection, string cmkName)
95+
{
96+
string KeyStoreProviderName = SqlColumnEncryptionAzureKeyVaultProvider.ProviderName;
97+
98+
string sql =
99+
$@"CREATE COLUMN MASTER KEY [{cmkName}]
100+
WITH (
101+
KEY_STORE_PROVIDER_NAME = N'{KeyStoreProviderName}',
102+
KEY_PATH = N'{s_akvUrl}'
103+
);";
104+
105+
using (SqlCommand command = sqlConnection.CreateCommand())
106+
{
107+
command.CommandText = sql;
108+
command.ExecuteNonQuery();
109+
}
110+
}
111+
112+
private static void createCEK(SqlConnection sqlConnection, string cmkName, string cekName, SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider)
113+
{
114+
string sql =
115+
$@"CREATE COLUMN ENCRYPTION KEY [{cekName}]
116+
WITH VALUES (
117+
COLUMN_MASTER_KEY = [{cmkName}],
118+
ALGORITHM = '{s_algorithm}',
119+
ENCRYPTED_VALUE = {GetEncryptedValue(sqlColumnEncryptionAzureKeyVaultProvider)}
120+
)";
121+
122+
using (SqlCommand command = sqlConnection.CreateCommand())
123+
{
124+
command.CommandText = sql;
125+
command.ExecuteNonQuery();
126+
}
127+
}
128+
129+
private static string GetEncryptedValue(SqlColumnEncryptionAzureKeyVaultProvider sqlColumnEncryptionAzureKeyVaultProvider)
130+
{
131+
byte[] plainTextColumnEncryptionKey = new byte[32];
132+
RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
133+
rngCsp.GetBytes(plainTextColumnEncryptionKey);
134+
135+
byte[] encryptedColumnEncryptionKey = sqlColumnEncryptionAzureKeyVaultProvider.EncryptColumnEncryptionKey(s_akvUrl, s_algorithm, plainTextColumnEncryptionKey);
136+
string EncryptedValue = string.Concat("0x", BitConverter.ToString(encryptedColumnEncryptionKey).Replace("-", string.Empty));
137+
return EncryptedValue;
138+
}
139+
140+
private static void createTbl(SqlConnection sqlConnection, string cekName, string tblName)
141+
{
142+
string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA_256";
143+
144+
string sql =
145+
$@"CREATE TABLE [dbo].[{tblName}]
146+
(
147+
[CustomerId] [int] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
148+
[FirstName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}'),
149+
[LastName] [nvarchar](50) COLLATE Latin1_General_BIN2 ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [{cekName}], ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = '{ColumnEncryptionAlgorithmName}')
150+
)";
151+
152+
using (SqlCommand command = sqlConnection.CreateCommand())
153+
{
154+
command.CommandText = sql;
155+
command.ExecuteNonQuery();
156+
}
157+
}
158+
159+
private static void insertData(SqlConnection sqlConnection, string tblName, CustomerRecord customer)
160+
{
161+
string insertSql = $"INSERT INTO [{tblName}] (CustomerId, FirstName, LastName) VALUES (@CustomerId, @FirstName, @LastName);";
162+
163+
using (SqlTransaction sqlTransaction = sqlConnection.BeginTransaction())
164+
using (SqlCommand sqlCommand = new SqlCommand(insertSql,
165+
connection: sqlConnection, transaction: sqlTransaction,
166+
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled))
167+
{
168+
sqlCommand.Parameters.AddWithValue(@"CustomerId", customer.Id);
169+
sqlCommand.Parameters.AddWithValue(@"FirstName", customer.FirstName);
170+
sqlCommand.Parameters.AddWithValue(@"LastName", customer.LastName);
171+
172+
sqlCommand.ExecuteNonQuery();
173+
sqlTransaction.Commit();
174+
}
175+
}
176+
177+
private static void verifyData(SqlConnection sqlConnection, string tblName, CustomerRecord customer)
178+
{
179+
// Test INPUT parameter on an encrypted parameter
180+
using (SqlCommand sqlCommand = new SqlCommand($"SELECT CustomerId, FirstName, LastName FROM [{tblName}] WHERE FirstName = @firstName",
181+
sqlConnection))
182+
{
183+
SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft");
184+
customerFirstParam.Direction = System.Data.ParameterDirection.Input;
185+
customerFirstParam.ForceColumnEncryption = true;
186+
187+
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
188+
{
189+
ValidateResultSet(sqlDataReader);
190+
}
191+
}
192+
}
193+
194+
private static void ValidateResultSet(SqlDataReader sqlDataReader)
195+
{
196+
Console.WriteLine(" * Row available: " + sqlDataReader.HasRows);
197+
198+
while (sqlDataReader.Read())
199+
{
200+
if (sqlDataReader.GetInt32(0) == 1)
201+
{
202+
Console.WriteLine(" * Employee Id received as sent: " + sqlDataReader.GetInt32(0));
203+
}
204+
else
205+
{
206+
Console.WriteLine("Employee Id didn't match");
207+
}
208+
209+
if (sqlDataReader.GetString(1) == @"Microsoft")
210+
{
211+
Console.WriteLine(" * Employee Firstname received as sent: " + sqlDataReader.GetString(1));
212+
}
213+
else
214+
{
215+
Console.WriteLine("Employee FirstName didn't match.");
216+
}
217+
218+
if (sqlDataReader.GetString(2) == @"Corporation")
219+
{
220+
Console.WriteLine(" * Employee LastName received as sent: " + sqlDataReader.GetString(2));
221+
}
222+
else
223+
{
224+
Console.WriteLine("Employee LastName didn't match.");
225+
}
226+
}
227+
}
228+
229+
private static void dropObjects(SqlConnection sqlConnection, string cmkName, string cekName, string tblName)
230+
{
231+
using (SqlCommand cmd = sqlConnection.CreateCommand())
232+
{
233+
cmd.CommandText = $@"IF EXISTS (select * from sys.objects where name = '{tblName}') BEGIN DROP TABLE [{tblName}] END";
234+
cmd.ExecuteNonQuery();
235+
cmd.CommandText = $@"IF EXISTS (select * from sys.column_encryption_keys where name = '{cekName}') BEGIN DROP COLUMN ENCRYPTION KEY [{cekName}] END";
236+
cmd.ExecuteNonQuery();
237+
cmd.CommandText = $@"IF EXISTS (select * from sys.column_master_keys where name = '{cmkName}') BEGIN DROP COLUMN MASTER KEY [{cmkName}] END";
238+
cmd.ExecuteNonQuery();
239+
}
240+
}
241+
242+
private class CustomerRecord
243+
{
244+
internal int Id { get; set; }
245+
internal string FirstName { get; set; }
246+
internal string LastName { get; set; }
247+
248+
public CustomerRecord(int id, string fName, string lName)
249+
{
250+
Id = id;
251+
FirstName = fName;
252+
LastName = lName;
253+
}
254+
}
255+
}
256+
}

release-notes/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@ The latest stable release is [Microsoft.Data.SqlClient 1.0](1.0).
66

77
- [Microsoft.Data.SqlClient 1.1](1.1)
88
- [Microsoft.Data.SqlClient 1.0](1.0)
9+
10+
# Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider Release Notes
11+
12+
The latest preview release is [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 1.0](add-ons/AzureKeyVaultProvider/1.0).
13+
14+
## Release Information
15+
16+
- [Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 1.0](add-ons/AzureKeyVaultProvider/1.0)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Release Notes
2+
3+
## Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider
4+
_**1.1.0-preview1.19292.1 released 18 October 2019**_
5+
6+
This is the initial public preview release of the new Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider namespace. This library contains the implementation of `Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider` for accessing Azure Key Vault, and the provider class is named as `SqlColumnEncryptionAzureKeyVaultProvider`.
7+
8+
### Working with SQLColumnEncryptionAzureKeyVaultProvider
9+
`SqlColumnEncryptionAzureKeyVaultProvider` is implemented for `Microsoft.Data.SqlClient` driver and supports .NET Framework 4.6+ and .NET Core 2.1+. This provider name identifier for this library is "**AZURE_KEY_VAULT**" and it is not registered in driver by default. Client applications must call `SqlConnection.RegisterColumnEncryptionKeyStoreProviders()` API only once in the lifetime of driver to register this custom provider by implementing a custom Authentication Callback mechanism.
10+
11+
Once the provider is registered, it can used to perform Always Encrypted operations by creating Column Master Key using Azure Key Vault Key Identifier URL.
12+
13+
A sample C# application to demonstrate Always Encrypted with Azure Key Vault can be download from samples directory: [AzureKeyVaultProviderExample.cs](..\..\..\..\doc\samples\AzureKeyVaultProviderExample.cs)
14+
15+
16+
## Target Platform Support
17+
18+
- .NET Framework 4.6+
19+
- .NET Core 2.1+ (Windows x86, Windows x64, Linux, macOS)
20+
21+
### Dependencies
22+
23+
#### .NET Framework
24+
25+
- Microsoft.Azure.KeyVault 3.0.4
26+
- Microsoft.Azure.KeyVault.WebKey 3.0.4
27+
- Microsoft.Rest.ClientRuntime 2.3.20
28+
- Microsoft.Rest.ClientRuntime.Azure 3.3.19
29+
30+
#### .NET Core
31+
32+
- Microsoft.Azure.KeyVault 3.0.4
33+
- Microsoft.Azure.KeyVault.WebKey 3.0.4
34+
- Microsoft.Rest.ClientRuntime 2.3.20
35+
- Microsoft.Rest.ClientRuntime.Azure 3.3.19
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 1.0 Releases
2+
3+
The following Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider 1.0 preview releases have been shipped:
4+
5+
| Release Date | Description | Notes |
6+
| :-- | :-- | :--: |
7+
| 2019/10/18 | 1.0.0-preview1.19292.1 | [release notes](1.0.0-preview1.md) |

0 commit comments

Comments
 (0)