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