SqlColumnEncryptionCspProvider.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. //------------------------------------------------------------------------------
  2. // <copyright file="SqlColumnEncryptionCspProvider.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. using Microsoft.Win32;
  18. /// <summary>
  19. /// Provides implementation similar to certificate store provider.
  20. /// A CEK encrypted with certificate store 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 SqlColumnEncryptionCspProvider : SqlColumnEncryptionKeyStoreProvider
  32. {
  33. /// <summary>
  34. /// Name for the CSP key store provider.
  35. /// </summary>
  36. public const string ProviderName = @"MSSQL_CSP_PROVIDER";
  37. /// <summary>
  38. /// RSA_OAEP is the only algorithm supported for encrypting/decrypting column encryption keys using this provider.
  39. /// For now, we are keeping all the providers in [....].
  40. /// </summary>
  41. private const string RSAEncryptionAlgorithmWithOAEP = @"RSA_OAEP";
  42. /// <summary>
  43. /// Hashing algoirthm used for signing
  44. /// </summary>
  45. private const string HashingAlgorithm = @"SHA256";
  46. /// <summary>
  47. /// Algorithm version
  48. /// </summary>
  49. private readonly byte[] _version = new byte[] { 0x01 };
  50. /// <summary>
  51. /// This function uses the asymmetric key specified by the key path
  52. /// and decrypts an encrypted CEK with RSA encryption algorithm.
  53. /// </summary>
  54. /// <param name="masterKeyPath">Complete path of an asymmetric key in CSP</param>
  55. /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
  56. /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key</param>
  57. /// <returns>Plain text column encryption key</returns>
  58. public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] encryptedColumnEncryptionKey)
  59. {
  60. // Validate the input parameters
  61. ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: true);
  62. if (null == encryptedColumnEncryptionKey)
  63. {
  64. throw SQL.NullEncryptedColumnEncryptionKey();
  65. }
  66. if (0 == encryptedColumnEncryptionKey.Length)
  67. {
  68. throw SQL.EmptyEncryptedColumnEncryptionKey();
  69. }
  70. // Validate encryptionAlgorithm
  71. ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true);
  72. // Create RSA Provider with the given CSP name and key name
  73. RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: true);
  74. // Validate whether the key is RSA one or not and then get the key size
  75. int keySizeInBytes = GetKeySize(rsaProvider);
  76. // Validate and decrypt the EncryptedColumnEncryptionKey
  77. // Format is
  78. // version + keyPathLength + ciphertextLength + keyPath + ciphervtext + signature
  79. //
  80. // keyPath is present in the encrypted column encryption key for identifying the original source of the asymmetric key pair and
  81. // we will not validate it against the data contained in the CMK metadata (masterKeyPath).
  82. // Validate the version byte
  83. if (encryptedColumnEncryptionKey[0] != _version[0])
  84. {
  85. throw SQL.InvalidAlgorithmVersionInEncryptedCEK(encryptedColumnEncryptionKey[0], _version[0]);
  86. }
  87. // Get key path length
  88. int currentIndex = _version.Length;
  89. UInt16 keyPathLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
  90. currentIndex += sizeof(UInt16);
  91. // Get ciphertext length
  92. UInt16 cipherTextLength = BitConverter.ToUInt16(encryptedColumnEncryptionKey, currentIndex);
  93. currentIndex += sizeof(UInt16);
  94. // Skip KeyPath
  95. // KeyPath exists only for troubleshooting purposes and doesnt need validation.
  96. currentIndex += keyPathLength;
  97. // validate the ciphertext length
  98. if (cipherTextLength != keySizeInBytes)
  99. {
  100. throw SQL.InvalidCiphertextLengthInEncryptedCEKCsp(cipherTextLength, keySizeInBytes, masterKeyPath);
  101. }
  102. // Validate the signature length
  103. // Signature length should be same as the key side for RSA PKCSv1.5
  104. int signatureLength = encryptedColumnEncryptionKey.Length - currentIndex - cipherTextLength;
  105. if (signatureLength != keySizeInBytes)
  106. {
  107. throw SQL.InvalidSignatureInEncryptedCEKCsp(signatureLength, keySizeInBytes, masterKeyPath);
  108. }
  109. // Get ciphertext
  110. byte[] cipherText = new byte[cipherTextLength];
  111. Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherText.Length);
  112. currentIndex += cipherTextLength;
  113. // Get signature
  114. byte[] signature = new byte[signatureLength];
  115. Buffer.BlockCopy(encryptedColumnEncryptionKey, currentIndex, signature, 0, signature.Length);
  116. // Compute the hash to validate the signature
  117. byte[] hash;
  118. using (SHA256Cng sha256 = new SHA256Cng())
  119. {
  120. sha256.TransformFinalBlock(encryptedColumnEncryptionKey, 0, encryptedColumnEncryptionKey.Length - signature.Length);
  121. hash = sha256.Hash;
  122. }
  123. Debug.Assert(hash != null, @"hash should not be null while decrypting encrypted column encryption key.");
  124. // Validate the signature
  125. if (!RSAVerifySignature(hash, signature, rsaProvider))
  126. {
  127. throw SQL.InvalidSignature(masterKeyPath);
  128. }
  129. // Decrypt the CEK
  130. return RSADecrypt(rsaProvider, cipherText);
  131. }
  132. /// <summary>
  133. /// This function uses the asymmetric key specified by the key path
  134. /// and encrypts CEK with RSA encryption algorithm.
  135. /// </summary>
  136. /// <param name="keyPath">Complete path of an asymmetric key in AKV</param>
  137. /// <param name="encryptionAlgorithm">Asymmetric Key Encryption Algorithm</param>
  138. /// <param name="columnEncryptionKey">Plain text column encryption key</param>
  139. /// <returns>Encrypted column encryption key</returns>
  140. public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
  141. {
  142. // Validate the input parameters
  143. ValidateNonEmptyCSPKeyPath(masterKeyPath, isSystemOp: false);
  144. if (null == columnEncryptionKey)
  145. {
  146. throw SQL.NullColumnEncryptionKey();
  147. }
  148. else if (0 == columnEncryptionKey.Length)
  149. {
  150. throw SQL.EmptyColumnEncryptionKey();
  151. }
  152. // Validate encryptionAlgorithm
  153. ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: false);
  154. // Create RSA Provider with the given CSP name and key name
  155. RSACryptoServiceProvider rsaProvider = CreateRSACryptoProvider(masterKeyPath, isSystemOp: false);
  156. // Validate whether the key is RSA one or not and then get the key size
  157. int keySizeInBytes = GetKeySize(rsaProvider);
  158. // Construct the encryptedColumnEncryptionKey
  159. // Format is
  160. // version + keyPathLength + ciphertextLength + ciphertext + keyPath + signature
  161. //
  162. // We currently only support one version
  163. byte[] version = new byte[] { _version[0] };
  164. // Get the Unicode encoded bytes of cultureinvariant lower case masterKeyPath
  165. byte[] masterKeyPathBytes = Encoding.Unicode.GetBytes(masterKeyPath.ToLowerInvariant());
  166. byte[] keyPathLength = BitConverter.GetBytes((Int16)masterKeyPathBytes.Length);
  167. // Encrypt the plain text
  168. byte[] cipherText = RSAEncrypt(rsaProvider, columnEncryptionKey);
  169. byte[] cipherTextLength = BitConverter.GetBytes((Int16)cipherText.Length);
  170. Debug.Assert(cipherText.Length == keySizeInBytes, @"cipherText length does not match the RSA key size");
  171. // Compute hash
  172. // SHA-2-256(version + keyPathLength + ciphertextLength + keyPath + ciphertext)
  173. byte[] hash;
  174. using (SHA256Cng sha256 = new SHA256Cng())
  175. {
  176. sha256.TransformBlock(version, 0, version.Length, version, 0);
  177. sha256.TransformBlock(keyPathLength, 0, keyPathLength.Length, keyPathLength, 0);
  178. sha256.TransformBlock(cipherTextLength, 0, cipherTextLength.Length, cipherTextLength, 0);
  179. sha256.TransformBlock(masterKeyPathBytes, 0, masterKeyPathBytes.Length, masterKeyPathBytes, 0);
  180. sha256.TransformFinalBlock(cipherText, 0, cipherText.Length);
  181. hash = sha256.Hash;
  182. }
  183. // Sign the hash
  184. byte[] signedHash = RSASignHashedData(hash, rsaProvider);
  185. Debug.Assert(signedHash.Length == keySizeInBytes, @"signed hash length does not match the RSA key size");
  186. Debug.Assert(RSAVerifySignature(hash, signedHash, rsaProvider), @"Invalid signature of the encrypted column encryption key computed.");
  187. // Construct the encrypted column encryption key
  188. // EncryptedColumnEncryptionKey = version + keyPathLength + ciphertextLength + keyPath + ciphertext + signature
  189. int encryptedColumnEncryptionKeyLength = version.Length + cipherTextLength.Length + keyPathLength.Length + cipherText.Length + masterKeyPathBytes.Length + signedHash.Length;
  190. byte[] encryptedColumnEncryptionKey = new byte[encryptedColumnEncryptionKeyLength];
  191. // Copy version byte
  192. int currentIndex = 0;
  193. Buffer.BlockCopy(version, 0, encryptedColumnEncryptionKey, currentIndex, version.Length);
  194. currentIndex += version.Length;
  195. // Copy key path length
  196. Buffer.BlockCopy(keyPathLength, 0, encryptedColumnEncryptionKey, currentIndex, keyPathLength.Length);
  197. currentIndex += keyPathLength.Length;
  198. // Copy ciphertext length
  199. Buffer.BlockCopy(cipherTextLength, 0, encryptedColumnEncryptionKey, currentIndex, cipherTextLength.Length);
  200. currentIndex += cipherTextLength.Length;
  201. // Copy key path
  202. Buffer.BlockCopy(masterKeyPathBytes, 0, encryptedColumnEncryptionKey, currentIndex, masterKeyPathBytes.Length);
  203. currentIndex += masterKeyPathBytes.Length;
  204. // Copy ciphertext
  205. Buffer.BlockCopy(cipherText, 0, encryptedColumnEncryptionKey, currentIndex, cipherText.Length);
  206. currentIndex += cipherText.Length;
  207. // copy the signature
  208. Buffer.BlockCopy(signedHash, 0, encryptedColumnEncryptionKey, currentIndex, signedHash.Length);
  209. return encryptedColumnEncryptionKey;
  210. }
  211. /// <summary>
  212. /// This function validates that the encryption algorithm is RSA_OAEP and if it is not,
  213. /// then throws an exception
  214. /// </summary>
  215. /// <param name="encryptionAlgorithm">Asymmetric key encryptio algorithm</param>
  216. /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
  217. private void ValidateEncryptionAlgorithm(string encryptionAlgorithm, bool isSystemOp)
  218. {
  219. // This validates that the encryption algorithm is RSA_OAEP
  220. if (null == encryptionAlgorithm)
  221. {
  222. throw SQL.NullKeyEncryptionAlgorithm(isSystemOp);
  223. }
  224. if (string.Equals(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, StringComparison.OrdinalIgnoreCase) != true)
  225. {
  226. throw SQL.InvalidKeyEncryptionAlgorithm(encryptionAlgorithm, RSAEncryptionAlgorithmWithOAEP, isSystemOp);
  227. }
  228. }
  229. /// <summary>
  230. /// Checks if the CSP key path is Empty or Null (and raises exception if they are).
  231. /// </summary>
  232. /// <param name="masterKeyPath">CSP key path.</param>
  233. /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
  234. private void ValidateNonEmptyCSPKeyPath(string masterKeyPath, bool isSystemOp)
  235. {
  236. if (string.IsNullOrWhiteSpace(masterKeyPath))
  237. {
  238. if (null == masterKeyPath)
  239. {
  240. throw SQL.NullCspKeyPath(isSystemOp);
  241. }
  242. else
  243. {
  244. throw SQL.InvalidCspPath(masterKeyPath, isSystemOp);
  245. }
  246. }
  247. }
  248. /// <summary>
  249. /// Encrypt the text using specified CSP key.
  250. /// </summary>
  251. /// <param name="masterKeyPath">CSP key path.</param>
  252. /// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
  253. /// <param name="columnEncryptionKey">Plain text Column Encryption Key.</param>
  254. /// <returns>Returns an encrypted blob or throws an exception if there are any errors.</returns>
  255. private byte[] RSAEncrypt(RSACryptoServiceProvider rscp, byte[] columnEncryptionKey)
  256. {
  257. Debug.Assert(columnEncryptionKey != null);
  258. Debug.Assert(rscp != null);
  259. return rscp.Encrypt(columnEncryptionKey, fOAEP: true);
  260. }
  261. /// <summary>
  262. /// Decrypt the text using specified CSP key.
  263. /// </summary>
  264. /// <param name="masterKeyPath">CSP key url.</param>
  265. /// <param name="encryptionAlgorithm">Encryption Algorithm.</param>
  266. /// <param name="encryptedColumnEncryptionKey">Encrypted Column Encryption Key.</param>
  267. /// <returns>Returns the decrypted plaintext Column Encryption Key or throws an exception if there are any errors.</returns>
  268. private byte[] RSADecrypt(RSACryptoServiceProvider rscp, byte[] encryptedColumnEncryptionKey)
  269. {
  270. Debug.Assert((encryptedColumnEncryptionKey != null) && (encryptedColumnEncryptionKey.Length != 0));
  271. Debug.Assert(rscp != null);
  272. return rscp.Decrypt(encryptedColumnEncryptionKey, fOAEP: true);
  273. }
  274. /// <summary>
  275. /// Generates signature based on RSA PKCS#v1.5 scheme using a specified CSP Key URL.
  276. /// </summary>
  277. /// <param name="dataToSign">Text to sign.</param>
  278. /// <param name="rscp">RSA Provider with a given key</param>
  279. /// <returns>Signature</returns>
  280. private byte[] RSASignHashedData(byte[] dataToSign, RSACryptoServiceProvider rscp)
  281. {
  282. Debug.Assert((dataToSign != null) && (dataToSign.Length != 0));
  283. Debug.Assert(rscp != null);
  284. return rscp.SignData(dataToSign, HashingAlgorithm);
  285. }
  286. /// <summary>
  287. /// Verifies the given RSA PKCSv1.5 signature.
  288. /// </summary>
  289. /// <param name="dataToVerify"></param>
  290. /// <param name="signature"></param>
  291. /// <param name="rscp">RSA Provider with a given key</param>
  292. /// <returns>true if signature is valid, false if it is not valid</returns>
  293. private bool RSAVerifySignature(byte[] dataToVerify, byte[] signature, RSACryptoServiceProvider rscp)
  294. {
  295. Debug.Assert((dataToVerify != null) && (dataToVerify.Length != 0));
  296. Debug.Assert((signature != null) && (signature.Length != 0));
  297. Debug.Assert(rscp != null);
  298. return rscp.VerifyData(dataToVerify, HashingAlgorithm, signature);
  299. }
  300. /// <summary>
  301. /// Gets the public Key size in bytes
  302. /// </summary>
  303. /// <param name="rscp">RSA Provider with a given key</param>
  304. /// <returns>Key size in bytes</returns>
  305. private int GetKeySize(RSACryptoServiceProvider rscp)
  306. {
  307. Debug.Assert(rscp != null);
  308. return rscp.KeySize / 8;
  309. }
  310. /// <summary>
  311. /// Creates a RSACryptoServiceProvider from the given key path which contains both CSP name and key name
  312. /// </summary>
  313. /// <param name="keyPath">key path in the format of [CAPI provider name]\[key name]</param>
  314. /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
  315. /// <returns></returns>
  316. private RSACryptoServiceProvider CreateRSACryptoProvider(string keyPath, bool isSystemOp)
  317. {
  318. // Get CNGProvider and the KeyID
  319. string cspProviderName;
  320. string keyName;
  321. GetCspProviderAndKeyName(keyPath, isSystemOp, out cspProviderName, out keyName);
  322. // Verify the existence of CSP and then get the provider type
  323. int providerType = GetProviderType(cspProviderName, keyPath, isSystemOp);
  324. // Create a new instance of CspParameters for an RSA container.
  325. CspParameters cspParams = new CspParameters(providerType, cspProviderName, keyName);
  326. cspParams.Flags = CspProviderFlags.UseExistingKey;
  327. RSACryptoServiceProvider rscp = null;
  328. try
  329. {
  330. //Create a new instance of RSACryptoServiceProvider
  331. rscp = new RSACryptoServiceProvider(cspParams);
  332. }
  333. catch (CryptographicException e)
  334. {
  335. const int KEYSETDOESNOTEXIST = -2146893802;
  336. if (e.HResult == KEYSETDOESNOTEXIST)
  337. {
  338. // Key does not exist
  339. throw SQL.InvalidCspKeyIdentifier(keyName, keyPath, isSystemOp);
  340. }
  341. else
  342. {
  343. // bubble up the exception
  344. throw;
  345. }
  346. }
  347. return rscp;
  348. }
  349. /// <summary>
  350. /// Extracts the CSP provider name and key name from the given key path
  351. /// </summary>
  352. /// <param name="keyPath">key path in the format of [CSP provider name]\[key name]</param>
  353. /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
  354. /// <param name="cspProviderName">output containing the CSP provider name</param>
  355. /// <param name="keyIdentifier">output containing the key name</param>
  356. private void GetCspProviderAndKeyName(string keyPath, bool isSystemOp, out string cspProviderName, out string keyIdentifier)
  357. {
  358. int indexOfSlash = keyPath.IndexOf(@"/");
  359. if (indexOfSlash == -1)
  360. {
  361. throw SQL.InvalidCspPath(keyPath, isSystemOp);
  362. }
  363. cspProviderName = keyPath.Substring(0, indexOfSlash);
  364. keyIdentifier = keyPath.Substring(indexOfSlash + 1, keyPath.Length - (indexOfSlash + 1));
  365. if (cspProviderName.Length == 0)
  366. {
  367. throw SQL.EmptyCspName(keyPath, isSystemOp);
  368. }
  369. if (keyIdentifier.Length == 0)
  370. {
  371. throw SQL.EmptyCspKeyId(keyPath, isSystemOp);
  372. }
  373. }
  374. /// <summary>
  375. /// Gets the provider type from a given CAPI provider name
  376. /// </summary>
  377. /// <param name="providerName">CAPI provider name</param>
  378. /// <param name="keyPath">key path in the format of [CSP provider name]\[key name]</param>
  379. /// <param name="isSystemOp">Indicates if ADO.NET calls or the customer calls the API</param>
  380. /// <returns></returns>
  381. private int GetProviderType(string providerName, string keyPath, bool isSystemOp)
  382. {
  383. string keyName = String.Format(@"SOFTWARE\Microsoft\Cryptography\Defaults\Provider\{0}", providerName);
  384. RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName);
  385. if (key == null)
  386. {
  387. throw SQL.InvalidCspName(providerName, keyPath, isSystemOp);
  388. }
  389. int providerType = (int)key.GetValue(@"Type");
  390. key.Close();
  391. return providerType;
  392. }
  393. }
  394. }