Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data / System / Data / SqlClient / SqlColumnEncryptionCngProvider.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SqlColumnEncryptionCngProvider.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">balnee</owner>
6 // <owner current="true" primary="false">krishnib</owner>
7 //------------------------------------------------------------------------------
8 namespace System.Data.SqlClient
9 {
10     using System;
11     using System.Text;
12     using System.Data.Common;
13     using System.Diagnostics;
14     using System.Globalization;
15     using System.Security;
16     using System.Security.Cryptography;
17
18     /// <summary>
19     /// Provides implementation similar to certificate store provider.
20     /// A CEK encrypted with certificate provider should be decryptable by this provider and vice versa.
21     /// 
22     /// Envolope Format for the encrypted column encryption key  
23     ///           version + keyPathLength + ciphertextLength + keyPath + ciphertext +  signature
24     /// version: A single byte indicating the format version.
25     /// keyPathLength: Length of the keyPath.
26     /// ciphertextLength: ciphertext length
27     /// keyPath: keyPath used to encrypt the column encryption key. This is only used for troubleshooting purposes and is not verified during decryption.
28     /// ciphertext: Encrypted column encryption key
29     /// signature: Signature of the entire byte array. Signature is validated before decrypting the column encryption key.
30     /// </summary>
31     public class SqlColumnEncryptionCngProvider : SqlColumnEncryptionKeyStoreProvider
32     {
33         /// <summary>
34         /// Name for the CNG key store provider.
35         /// </summary>
36         public const string ProviderName = @"MSSQL_CNG_STORE";
37
38         /// <summary>
39         /// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
40         /// For now, we are keeping all the providers in [....].
41         /// </summary>
42         private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
43
44         /// <summary>
45         /// Algorithm version
46         /// </summary>
47         private readonly byte[] _version = new byte[] { 0x01 };
48
49         /// <summary>
50         /// This function uses the asymmetric key specified by the key path
51         /// and decrypts an encrypted CEK with RSA encryption algorithm.
52         /// </summary>
53         /// <param name="masterKeyPath">Complete path of an asymmetric key in CNG</param>
54         /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
55         /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param>
56         /// <returns>Plain text column encryption key</returns>
57         public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
58         {
59             // Validate the input parameters
60             ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: true);
61
62             if (null == encryptedColumnEncryptionKey)
63             {
64                 throw SQL.NullEncryptedColumnEncryptionKey();
65             }
66
67             if (0 == encryptedColumnEncryptionKey.Length)
68             {
69                 throw SQL.EmptyEncryptedColumnEncryptionKey();
70             }
71
72             // Validate encryptionAlgorithm
73             ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
74
75             // Create RSA Provider with the given CNG name and key name
76             RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: true);
77
78             // Validate whether the key is RSA one or not and then get the key size
79             int keySizeInBytes = GetKeySize(rsaCngProvider);
80
81             // Validate and decrypt the EncryptedColumnEncryptionKey
82             // Format is 
83             //           version + keyPathLength + ciphertextLength + keyPath + ciphervtext +  signature
84             //
85             // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and 
86             // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
87
88             // Validate the version byte
89             if (encryptedColumnEncryptionKey[0] != _version[0])
90             {
91                 throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
92             }
93
94             // Get key path length
95             int currentIndex = _version.Length;
96             UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
97             currentIndex += sizeof(UInt16);
98
99             // Get ciphertext length
100             UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
101             currentIndex += sizeof(UInt16);
102
103             // Skip KeyPath
104             // KeyPath exists only for troubleshooting purposes and doesnt need validation.
105             currentIndex += keyPathLength;
106
107             // validate the ciphertext length
108             if (cipherTextLength != keySizeInBytes)
109             {
110                 throw SQL.InvalidCiphertextLengthInEncryptedCEKCng(cipherTextLength, keySizeInBytes, masterKeyPath);
111             }
112
113             // Validate the signature length
114             // Signature length should be same as the key side for RSA PKCSv1.5
115             int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength;
116             if (signatureLength != keySizeInBytes)
117             {
118                 throw SQL.InvalidSignatureInEncryptedCEKCng(signatureLength, keySizeInBytes, masterKeyPath);
119             }
120
121             // Get ciphertext
122             byte[] cipherText = new byte[cipherTextLength];
123             Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length);
124             currentIndex += cipherTextLength;
125
126             // Get signature
127             byte[] signature = new byte[signatureLength];
128             Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length);
129
130             // Compute the hash to validate the signature
131             byte[] hash;
132             using (SHA256Cng sha256 = new SHA256Cng())
133             {
134                 sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length);
135                 hash = sha256.Hash;
136             }
137
138             Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key.");
139
140             // Validate the signature
141             if (!RSAVerifySignature(hash, signature, rsaCngProvider))
142             {
143                 throw SQL.InvalidSignature(masterKeyPath);
144             }
145
146             // Decrypt the CEK
147             return RSADecrypt(rsaCngProvider, cipherText);
148         }
149
150         /// <summary>
151         /// This function uses the asymmetric key specified by the key path
152         /// and encrypts CEK with RSA encryption algorithm.
153         /// </summary>
154         /// <param name="keyPath">Complete path of an asymmetric key in AKV</param>
155         /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
156         /// <param name="columnEncryptionKey">Plain text column encryption key</param>
157         /// <returns>Encrypted column encryption key</returns>
158         public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
159         {
160             // Validate the input parameters
161             ValidateNonEmptyKeyPath(masterKeyPath, isSystemOp: false);
162
163             if (null == columnEncryptionKey)
164             {
165                 throw SQL.NullColumnEncryptionKey();
166             }
167             else if (0 == columnEncryptionKey.Length)
168             {
169                 throw SQL.EmptyColumnEncryptionKey();
170             }
171
172             // Validate encryptionAlgorithm
173             ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
174
175             // CreateCNGProviderWithKey
176             RSACng rsaCngProvider = CreateRSACngProvider(masterKeyPath, isSystemOp: false);
177
178             // Validate whether the key is RSA one or not and then get the key size
179             int keySizeInBytes = GetKeySize(rsaCngProvider);
180
181             // Construct the encryptedColumnEncryptionKey
182             // Format is 
183             //          version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
184             //
185             // We currently only support one version
186             byte[] version = new byte[] { _version[0] };
187
188             // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
189             byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
190             byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
191
192             // Encrypt the plain text
193             byte[] cipherText = RSAEncrypt(rsaCngProvider, columnEncryptionKey);
194             byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
195             Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
196
197             // Compute hash
198             // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext) 
199             byte[] hash;
200             using (SHA256Cng sha256 = new SHA256Cng())
201             {
202                 sha256.TransformBlock(version, 0, version.Length, version, 0);
203                 sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0);
204                 sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0);
205                 sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0);
206                 sha256.TransformFinalBlock(cipherText, 0, cipherText.Length);
207                 hash = sha256.Hash;
208             }
209
210             // Sign the hash
211             byte[] signedHash = RSASignHashedData(hash, rsaCngProvider);
212             Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size");
213             Debug.Assert(RSAVerifySignature(hash, signedHash, rsaCngProvider), @"Invalid signature of the encrypted column encryption key computed.");
214
215             // Construct the encrypted column encryption key
216             // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext +  signature
217             int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length;
218             byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
219
220             // Copy version byte
221             int currentIndex = 0;
222             Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length);
223             currentIndex += version.Length;
224
225             // Copy key path length
226             Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length);
227             currentIndex += keyPathLength.Length;
228
229             // Copy ciphertext length
230             Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length);
231             currentIndex += cipherTextLength.Length;
232
233             // Copy key path
234             Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length);
235             currentIndex += masterKeyPathBytes.Length;
236
237             // Copy ciphertext
238             Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length);
239             currentIndex += cipherText.Length;
240
241             // copy the signature
242             Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length);
243
244             return encryptedColumnEncryptionKey;
245         }
246
247         /// <summary>
248         /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
249         /// then throws an exception
250         /// </summary>
251         /// <param name="encryptionAlgorithm">Asymmetric key encryptio algorithm</param>
252         /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
253         private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
254         {
255             // This validates that the encryption algorithm is RSA_OAEP
256             if (null == encryptionAlgorithm)
257             {
258                 throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
259             }
260
261             if (!string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase))
262             {
263                 throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
264             }
265         }
266
267         /// <summary>
268         /// Checks if the CNG key path is Empty or Null (and raises exception if they are).
269         /// </summary>
270         /// <param name="masterKeyPath">keypath containing the CNG provider name and key name</param>
271         /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
272         private void ValidateNonEmptyKeyPath(string masterKeyPath, bool isSystemOp)
273         {
274             if (string.IsNullOrWhiteSpace(masterKeyPath))
275             {
276                 if (null == masterKeyPath)
277                 {
278                     throw SQL.NullCngKeyPath(isSystemOp);
279                 }
280                 else
281                 {
282                     throw SQL.InvalidCngPath(masterKeyPath, isSystemOp);
283                 }
284             }
285         }
286         
287         /// <summary>
288         /// Encrypt the text using specified CNG key.
289         /// </summary>
290         /// <param name="rsaCngProvider">RSA CNG Provider.</param>
291         /// <param name="columnEncryptionKey">Plain text Column Encryption Key.</param>
292         /// <returns>Returns an encrypted blob or throws an exception if there are any errors.</returns>
293         private byte[] RSAEncrypt(RSACng rsaCngProvider, byte[] columnEncryptionKey)
294         {
295             Debug.Assert(columnEncryptionKey != null);
296             Debug.Assert(rsaCngProvider != null);
297
298             return rsaCngProvider.Encrypt(columnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
299         }
300
301         /// <summary>
302         /// Decrypt the text using the specified CNG key.
303         /// </summary>
304         /// <param name="rsaCngProvider">RSA CNG Provider.</param>
305         /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key.</param>
306         /// <returns>Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.</returns>
307         private byte[] RSADecrypt(RSACng rsaCngProvider, byte[] encryptedColumnEncryptionKey)
308         {
309             Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
310             Debug.Assert(rsaCngProvider != null);
311
312             return rsaCngProvider.Decrypt(encryptedColumnEncryptionKey, RSAEncryptionPadding.OaepSHA1);
313         }
314
315         /// <summary>
316         /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CNG Key. 
317         /// </summary>
318         /// <param name="dataToSign">Text to sign.</param>
319         /// <param name="rsaCngProvider">RSA CNG Provider.</param>
320         /// <returns>Signature</returns>
321         private byte[] RSASignHashedData(byte[] dataToSign, RSACng rsaCngProvider)
322         {
323             Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
324             Debug.Assert(rsaCngProvider != null);
325
326             return rsaCngProvider.SignData(dataToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
327         }
328
329         /// <summary>
330         /// Verifies the given RSA PKCSv1.5 signature.
331         /// </summary>
332         /// <param name="dataToVerify"></param>
333         /// <param name="signature"></param>
334         /// <param name="rsaCngProvider">RSA CNG Provider.</param>
335         /// <returns>true if signature is valid, false if it is not valid</returns>
336         private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACng rsaCngProvider)
337         {
338             Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0));
339             Debug.Assert((signature != null) && (signature.Length != 0));
340             Debug.Assert(rsaCngProvider != null);
341
342             return rsaCngProvider.VerifyData(dataToVerify, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
343         }
344
345         /// <summary>
346         /// Gets the public Key size in bytes
347         /// </summary>
348         /// <param name="rsaCngProvider">RSA CNG Provider.</param>
349         /// <returns>Key size in bytes</returns>
350         private int GetKeySize(RSACng rsaCngProvider)
351         {
352             Debug.Assert(rsaCngProvider != null);
353
354             return rsaCngProvider.KeySize / 8; // Convert from bits to byte
355         }
356
357         /// <summary>
358         /// Creates a RSACng object from the given keyName
359         /// </summary>
360         /// <param name="keyPath"></param>
361         /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
362         /// <returns></returns>
363         private RSACng CreateRSACngProvider(string keyPath, bool isSystemOp)
364         {
365             // Get CNGProvider and the KeyID
366             string cngProviderName;
367             string keyIdentifier;
368             GetCngProviderAndKeyId(keyPath, isSystemOp, out cngProviderName, out keyIdentifier);
369
370             CngProvider cngProvider = new CngProvider(cngProviderName);
371             CngKey cngKey;
372
373             try
374             {
375                 cngKey = CngKey.Open(keyIdentifier, cngProvider);
376             }
377             catch (CryptographicException)
378             {
379                 throw SQL.InvalidCngKey(keyPath, cngProviderName, keyIdentifier, isSystemOp);
380             }
381
382             return new RSACng(cngKey);
383         }
384
385         /// <summary>
386         /// Extracts the CNG provider and key name from the key path
387         /// </summary>
388         /// <param name="masterKeyPath">keypath in the format [CNG Provider]\[KeyName]</param>
389         /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
390         /// <param name="cngProvider">CNG Provider</param>
391         /// <param name="keyIdentifier">Key identifier inside the CNG provider</param>
392         private void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out string cngProvider, out string keyIdentifier)
393         {
394             int indexOfSlash = keyPath.IndexOf(@"/");
395             if (indexOfSlash == -1)
396             {
397                 throw SQL.InvalidCngPath(keyPath, isSystemOp);
398             }
399
400             cngProvider = keyPath.Substring(0, indexOfSlash);
401             keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
402
403             if (cngProvider.Length == 0)
404             {
405                 throw SQL.EmptyCngName(keyPath, isSystemOp);
406             }
407
408             if (keyIdentifier.Length == 0)
409             {
410                 throw SQL.EmptyCngKeyId(keyPath, isSystemOp);
411             }
412         }
413     }
414 }