PageRenderTime 29ms CodeModel.GetById 1ms app.highlight 17ms RepoModel.GetById 7ms app.codeStats 0ms

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