PageRenderTime 2021ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://github.com/nwendel/ravendb
C# | 158 lines | 126 code | 20 blank | 12 comment | 16 complexity | 03d1522452b9e057ecbfa02a63650923 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Configuration;
  4. using System.Linq;
  5. using System.Security.Cryptography;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Raven.Abstractions.Data;
  10. using Raven.Abstractions.Extensions;
  11. using Raven.Database;
  12. using Raven.Database.Plugins;
  13. using Raven.Json.Linq;
  14. namespace Raven.Bundles.Encryption.Settings
  15. {
  16. internal static class EncryptionSettingsManager
  17. {
  18. private static readonly string EncryptionSettingsKeyInExtensionsState = Guid.NewGuid().ToString();
  19. public static EncryptionSettings GetEncryptionSettingsForDatabase(DocumentDatabase database)
  20. {
  21. var result = (EncryptionSettings)database.ExtensionsState.GetOrAdd(EncryptionSettingsKeyInExtensionsState, _ =>
  22. {
  23. var type = GetTypeFromName(database.Configuration.Settings[Constants.AlgorithmTypeSetting]);
  24. var key = GetKeyFromBase64(database.Configuration.Settings[Constants.EncryptionKeySetting], database.Configuration.Encryption.EncryptionKeyBitsPreference);
  25. var encryptIndexes = GetEncryptIndexesFromString(database.Configuration.Settings[Constants.EncryptIndexes], true);
  26. return new EncryptionSettings(key, type, encryptIndexes, database.Configuration.Encryption.EncryptionKeyBitsPreference);
  27. });
  28. return result;
  29. }
  30. /// <summary>
  31. /// A wrapper around Convert.FromBase64String, with extra validation and relevant exception messages.
  32. /// </summary>
  33. private static byte[] GetKeyFromBase64(string base64, int defaultEncryptionKeySize)
  34. {
  35. if (string.IsNullOrWhiteSpace(base64))
  36. throw new ConfigurationErrorsException("The " + Constants.EncryptionKeySetting + " setting must be set to an encryption key. "
  37. + "The key should be in base 64, and should be at least " + Constants.MinimumAcceptableEncryptionKeyLength
  38. + " bytes long. You may use EncryptionSettings.GenerateRandomEncryptionKey() to generate a key.\n"
  39. + "If you'd like, here's a key that was randomly generated:\n"
  40. + "<add key=\"Raven/Encryption/Key\" value=\""
  41. + Convert.ToBase64String(EncryptionSettings.GenerateRandomEncryptionKey(defaultEncryptionKeySize))
  42. + "\" />");
  43. try
  44. {
  45. var result = Convert.FromBase64String(base64);
  46. if (result.Length < Constants.MinimumAcceptableEncryptionKeyLength)
  47. throw new ConfigurationErrorsException("The " + Constants.EncryptionKeySetting + " setting must be at least "
  48. + Constants.MinimumAcceptableEncryptionKeyLength + " bytes long.");
  49. return result;
  50. }
  51. catch (FormatException e)
  52. {
  53. throw new ConfigurationErrorsException("The " + Constants.EncryptionKeySetting + " setting has an invalid base 64 value.", e);
  54. }
  55. }
  56. /// <summary>
  57. /// A wrapper around Type.GetType, with extra validation and a default value.
  58. /// </summary>
  59. private static Type GetTypeFromName(string typeName)
  60. {
  61. if (string.IsNullOrWhiteSpace(typeName))
  62. return Constants.DefaultCryptoServiceProvider;
  63. var result = Type.GetType(typeName);
  64. if (result == null)
  65. throw new ConfigurationErrorsException("Unknown type for encryption: " + typeName);
  66. if (!result.IsSubclassOf(typeof(System.Security.Cryptography.SymmetricAlgorithm)))
  67. throw new ConfigurationErrorsException("The encryption algorithm type must be a subclass of System.Security.Cryptography.SymmetricAlgorithm.");
  68. if (result.IsAbstract)
  69. throw new ConfigurationErrorsException("Cannot use an abstract type for an encryption algorithm.");
  70. return result;
  71. }
  72. /// <summary>
  73. /// Uses an encrypted document to verify that the encryption key is correct and decodes it to the right value.
  74. /// </summary>
  75. public static void VerifyEncryptionKey(DocumentDatabase database, EncryptionSettings settings)
  76. {
  77. JsonDocument doc;
  78. try
  79. {
  80. doc = database.Documents.Get(Constants.InDatabaseKeyVerificationDocumentName, null);
  81. }
  82. catch (CryptographicException e)
  83. {
  84. throw new ConfigurationErrorsException("The database is encrypted with a different key and/or algorithm than the ones "
  85. + "currently in the configuration file.", e);
  86. }
  87. if (doc != null)
  88. {
  89. var ravenJTokenEqualityComparer = new RavenJTokenEqualityComparer();
  90. if (!ravenJTokenEqualityComparer.Equals(doc.DataAsJson,Constants.InDatabaseKeyVerificationDocumentContents))
  91. throw new ConfigurationErrorsException("The database is encrypted with a different key and/or algorithm than the ones "
  92. + "currently in the configuration file.");
  93. }
  94. else
  95. {
  96. // This is the first time the database is loaded.
  97. if (EncryptedDocumentsExist(database))
  98. throw new InvalidOperationException("The database already has existing documents, you cannot start using encryption now.");
  99. var clonedDoc = (RavenJObject)Constants.InDatabaseKeyVerificationDocumentContents.CreateSnapshot();
  100. database.Documents.Put(Constants.InDatabaseKeyVerificationDocumentName, null, clonedDoc, new RavenJObject(), null);
  101. }
  102. }
  103. private static bool EncryptedDocumentsExist(DocumentDatabase database)
  104. {
  105. const int pageSize = 10;
  106. int index = 0;
  107. while (true)
  108. {
  109. var array = database.Documents.GetDocuments(index, index + pageSize, null, CancellationToken.None);
  110. if (array.Length == 0)
  111. {
  112. // We've gone over all the documents in the database, and none of them are encrypted.
  113. return false;
  114. }
  115. if (array.All(x => EncryptionSettings.DontEncrypt(x.Value<RavenJObject>("@metadata").Value<string>("@id"))))
  116. {
  117. index += array.Length;
  118. continue;
  119. }
  120. // Found a document which is encrypted
  121. return true;
  122. }
  123. }
  124. private static bool GetEncryptIndexesFromString(string value, bool defaultValue)
  125. {
  126. if (string.IsNullOrWhiteSpace(value))
  127. return defaultValue;
  128. try
  129. {
  130. return Convert.ToBoolean(value);
  131. }
  132. catch (Exception e)
  133. {
  134. throw new ConfigurationErrorsException("Invalid boolean value for setting EncryptIndexes: " + value, e);
  135. }
  136. }
  137. }
  138. }