PageRenderTime 83ms CodeModel.GetById 48ms RepoModel.GetById 0ms app.codeStats 1ms

/ToMigrate/Raven.Database/Bundles/Encryption/Settings/EncryptionSettingsManager.cs

http://github.com/ayende/ravendb
C# | 246 lines | 199 code | 31 blank | 16 comment | 29 complexity | 6856741b52fcdccd6260e8e0a57a2751 MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Configuration;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Security.Cryptography;
  6. using System.Threading;
  7. using Raven.Abstractions.Data;
  8. using Raven.Abstractions.Json.Linq;
  9. using Raven.Abstractions.Logging;
  10. using Raven.Bundles.Encryption.Settings;
  11. using Raven.Database.Common;
  12. using Raven.Database.Config;
  13. using Raven.Database.FileSystem;
  14. using Raven.Database.Server.Abstractions;
  15. using Raven.Json.Linq;
  16. namespace Raven.Database.Bundles.Encryption.Settings
  17. {
  18. internal static class EncryptionSettingsManager
  19. {
  20. private static readonly string EncryptionSettingsKeyInExtensionsState = Guid.NewGuid().ToString();
  21. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  22. public static EncryptionSettings GetEncryptionSettingsForResource(IResourceStore resource)
  23. {
  24. var result = (EncryptionSettings)resource.ExtensionsState.GetOrAdd(EncryptionSettingsKeyInExtensionsState, _ =>
  25. {
  26. var type = GetTypeFromName(resource.Configuration.Encryption.AlgorithmType);
  27. var key = GetKeyFromBase64(resource.Configuration.Encryption.EncryptionKey, resource.Configuration.Encryption.EncryptionKeyBitsPreference);
  28. var encryptIndexes = resource.Configuration.Encryption.EncryptIndexes;
  29. return new EncryptionSettings(key, type, encryptIndexes, resource.Configuration.Encryption.EncryptionKeyBitsPreference);
  30. });
  31. return result;
  32. }
  33. /// <summary>
  34. /// A wrapper around Convert.FromBase64String, with extra validation and relevant exception messages.
  35. /// </summary>
  36. private static byte[] GetKeyFromBase64(string base64, int defaultEncryptionKeySize)
  37. {
  38. if (string.IsNullOrWhiteSpace(base64))
  39. throw new ConfigurationErrorsException("The " + RavenConfiguration.GetKey(x => x.Encryption.EncryptionKey) + " setting must be set to an encryption key. "
  40. + "The key should be in base 64, and should be at least " + Constants.MinimumAcceptableEncryptionKeyLength
  41. + " bytes long. You may use EncryptionSettings.GenerateRandomEncryptionKey() to generate a key.\n"
  42. + "If you'd like, here's a key that was randomly generated:\n"
  43. + "<add key=\"Raven/Encryption/Key\" value=\""
  44. + Convert.ToBase64String(EncryptionSettings.GenerateRandomEncryptionKey(defaultEncryptionKeySize))
  45. + "\" />");
  46. try
  47. {
  48. var result = Convert.FromBase64String(base64);
  49. if (result.Length < Constants.MinimumAcceptableEncryptionKeyLength)
  50. throw new ConfigurationErrorsException("The " + RavenConfiguration.GetKey(x => x.Encryption.EncryptionKey) + " setting must be at least "
  51. + Constants.MinimumAcceptableEncryptionKeyLength + " bytes long.");
  52. return result;
  53. }
  54. catch (FormatException e)
  55. {
  56. throw new ConfigurationErrorsException("The " + RavenConfiguration.GetKey(x => x.Encryption.EncryptionKey) + " setting has an invalid base 64 value.", e);
  57. }
  58. }
  59. /// <summary>
  60. /// A wrapper around Type.GetType, with extra validation and a default value.
  61. /// </summary>
  62. private static Type GetTypeFromName(string typeName)
  63. {
  64. if (string.IsNullOrWhiteSpace(typeName))
  65. return Constants.DefaultCryptoServiceProvider;
  66. var result = Type.GetType(typeName);
  67. if (result == null)
  68. throw new ConfigurationErrorsException("Unknown type for encryption: " + typeName);
  69. if (!result.IsSubclassOf(typeof(System.Security.Cryptography.SymmetricAlgorithm)))
  70. throw new ConfigurationErrorsException("The encryption algorithm type must be a subclass of System.Security.Cryptography.SymmetricAlgorithm.");
  71. if (result.IsAbstract)
  72. throw new ConfigurationErrorsException("Cannot use an abstract type for an encryption algorithm.");
  73. return result;
  74. }
  75. /// <summary>
  76. /// Uses an encrypted document to verify that the encryption key is correct and decodes it to the right value.
  77. /// </summary>
  78. public static void VerifyEncryptionKey(RavenFileSystem fileSystem, EncryptionSettings settings)
  79. {
  80. RavenJObject config = null;
  81. try
  82. {
  83. fileSystem.Storage.Batch(accessor =>
  84. {
  85. try
  86. {
  87. config = accessor.GetConfig(Constants.InResourceKeyVerificationDocumentName);
  88. }
  89. catch (FileNotFoundException)
  90. {
  91. }
  92. });
  93. }
  94. catch (CryptographicException e)
  95. {
  96. throw new ConfigurationErrorsException("The file system is encrypted with a different key and/or algorithm than the ones "
  97. + "currently in the configuration file.", e);
  98. }
  99. if (config != null)
  100. {
  101. if (!RavenJTokenEqualityComparer.Default.Equals(config, Constants.InResourceKeyVerificationDocumentContents))
  102. throw new ConfigurationErrorsException("The file system is encrypted with a different key and/or algorithm than the ones is currently configured");
  103. }
  104. else
  105. {
  106. // This is the first time the file system is loaded.
  107. if (EncryptedFileExist(fileSystem))
  108. throw new InvalidOperationException("The file system already has existing files, you cannot start using encryption now.");
  109. var clonedDoc = (RavenJObject)Constants.InResourceKeyVerificationDocumentContents.CreateSnapshot();
  110. fileSystem.Storage.Batch(accessor => accessor.SetConfig(Constants.InResourceKeyVerificationDocumentName, clonedDoc));
  111. }
  112. }
  113. /// <summary>
  114. /// Uses an encrypted document to verify that the encryption key is correct and decodes it to the right value.
  115. /// </summary>
  116. public static void VerifyEncryptionKey(DocumentDatabase database, EncryptionSettings settings)
  117. {
  118. JsonDocument doc;
  119. try
  120. {
  121. doc = database.Documents.Get(Constants.InResourceKeyVerificationDocumentName);
  122. }
  123. catch (Exception e)
  124. {
  125. if (e is CryptographicException)
  126. {
  127. throw new ConfigurationErrorsException("The database is encrypted with a different key and/or algorithm than the ones "
  128. + "currently in the configuration file.", e);
  129. }
  130. if (settings.Codec.UsingSha1)
  131. throw;
  132. if (log.IsDebugEnabled)
  133. log.Debug("Couldn't decrypt the database using MD5. Trying with SHA1.");
  134. settings.Codec.UseSha1();
  135. VerifyEncryptionKey(database, settings);
  136. return;
  137. }
  138. if (doc != null)
  139. {
  140. if (!RavenJTokenEqualityComparer.Default.Equals(doc.DataAsJson, Constants.InResourceKeyVerificationDocumentContents))
  141. throw new ConfigurationErrorsException("The database is encrypted with a different key and/or algorithm than the ones "
  142. + "currently in the configuration file.");
  143. }
  144. else
  145. {
  146. // This is the first time the database is loaded.
  147. if (EncryptedDocumentsExist(database))
  148. throw new InvalidOperationException("The database already has existing documents, you cannot start using encryption now.");
  149. var clonedDoc = (RavenJObject)Constants.InResourceKeyVerificationDocumentContents.CreateSnapshot();
  150. database.Documents.Put(Constants.InResourceKeyVerificationDocumentName, null, clonedDoc, new RavenJObject(), null);
  151. }
  152. }
  153. private static bool EncryptedDocumentsExist(DocumentDatabase database)
  154. {
  155. const int pageSize = 10;
  156. int index = 0;
  157. while (true)
  158. {
  159. var array = database.Documents.GetDocumentsAsJson(index, index + pageSize, null, CancellationToken.None);
  160. if (array.Length == 0)
  161. {
  162. // We've gone over all the documents in the database, and none of them are encrypted.
  163. return false;
  164. }
  165. if (array.All(x => EncryptionSettings.DontEncrypt(x.Value<RavenJObject>("@metadata").Value<string>("@id"))))
  166. {
  167. index += array.Length;
  168. continue;
  169. }
  170. // Found a document which is encrypted
  171. return true;
  172. }
  173. }
  174. private static bool EncryptedFileExist(RavenFileSystem fileSystem)
  175. {
  176. const int pageSize = 10;
  177. var start = Guid.Empty;
  178. bool foundEncryptedDoc = false;
  179. while (true)
  180. {
  181. var foundMoreDocs = false;
  182. fileSystem.Storage.Batch(accessor =>
  183. {
  184. var fileHeaders = accessor.GetFilesAfter(start, pageSize);
  185. foreach (var fileHeader in fileHeaders)
  186. {
  187. foundMoreDocs = true;
  188. if (EncryptionSettings.DontEncrypt(fileHeader.Name) == false)
  189. {
  190. foundEncryptedDoc = true;
  191. break;
  192. }
  193. start = fileHeader.Etag;
  194. }
  195. });
  196. if (foundEncryptedDoc || foundMoreDocs == false)
  197. break;
  198. }
  199. return foundEncryptedDoc;
  200. }
  201. private static bool GetEncryptIndexesFromString(string value, bool defaultValue)
  202. {
  203. if (string.IsNullOrWhiteSpace(value))
  204. return defaultValue;
  205. try
  206. {
  207. return Convert.ToBoolean(value);
  208. }
  209. catch (Exception e)
  210. {
  211. throw new ConfigurationErrorsException("Invalid boolean value for setting EncryptIndexes: " + value, e);
  212. }
  213. }
  214. }
  215. }