diff --git a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj index acfe2486b..8b3915057 100644 --- a/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj +++ b/src/Orchard.Tests.Modules/Orchard.Tests.Modules.csproj @@ -154,6 +154,7 @@ + diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs index 9e35a845a..601bbd69d 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AccountControllerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -26,20 +27,19 @@ using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; using Orchard.Security.Permissions; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.UI.Notify; using Orchard.Users.Controllers; using Orchard.Users.Handlers; using Orchard.Users.Models; using Orchard.Users.Services; -using Orchard.Users.ViewModels; using Orchard.Settings; using Orchard.Core.Settings.Services; using Orchard.Tests.Messaging; using Orchard.Environment.Configuration; using Orchard.Core.Settings.Models; using Orchard.Core.Settings.Handlers; -using Orchard.Messaging.Models; using System.Collections.Specialized; namespace Orchard.Tests.Modules.Users.Controllers { @@ -74,11 +74,14 @@ namespace Orchard.Tests.Modules.Users.Controllers { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _authorizer = new Mock(); builder.RegisterInstance(_authorizer.Object); diff --git a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs index 6f6d395cb..ca2f20493 100644 --- a/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs +++ b/src/Orchard.Tests.Modules/Users/Controllers/AdminControllerTests.cs @@ -26,6 +26,7 @@ using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; using Orchard.Security.Permissions; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.UI.Notify; using Orchard.Users.Controllers; @@ -69,7 +70,8 @@ namespace Orchard.Tests.Modules.Users.Controllers { builder.RegisterInstance(new Mock().Object); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _authorizer = new Mock(); builder.RegisterInstance(_authorizer.Object); diff --git a/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs b/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs index efa354cc8..34b572dc7 100644 --- a/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs +++ b/src/Orchard.Tests.Modules/Users/Services/UserServiceTests.cs @@ -1,5 +1,4 @@ using System; -using System.Web.Security; using System.Xml.Linq; using Autofac; using Moq; @@ -21,6 +20,7 @@ using Orchard.Environment.Extensions; using Orchard.Messaging.Events; using Orchard.Messaging.Services; using Orchard.Security; +using Orchard.Security.Providers; using Orchard.Tests.Stubs; using Orchard.Tests.Utility; using Orchard.Users.Handlers; @@ -96,7 +96,9 @@ namespace Orchard.Tests.Modules.Users.Services { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterInstance(new ShellSettings { Name = "Alpha", RequestUrlHost = "wiki.example.com", RequestUrlPrefix = "~/foo" }); + + builder.RegisterType().As(); + builder.RegisterInstance(ShellSettingsUtility.CreateEncryptionEnabled()); _session = _sessionFactory.OpenSession(); builder.RegisterInstance(new TestSessionLocator(_session)).As(); @@ -121,25 +123,5 @@ namespace Orchard.Tests.Modules.Users.Services { Assert.That(username, Is.EqualTo("foo")); Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow)); } - - [Test] - public void NonceShouldNotBeUsedOnAnotherTenant() { - var user = _membershipService.CreateUser(new CreateUserParams("foo", "66554321", "foo@bar.com", "", "", true)); - var nonce = _userService.CreateNonce(user, new TimeSpan(1, 0, 0)); - - Assert.That(nonce, Is.Not.Empty); - - string username; - DateTime validateByUtc; - - _container.Resolve().Name = "Beta"; - - var result = _userService.DecryptNonce(nonce, out username, out validateByUtc); - - Assert.That(result, Is.False); - Assert.That(username, Is.EqualTo("foo")); - Assert.That(validateByUtc, Is.GreaterThan(_clock.UtcNow)); - } - } } diff --git a/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs b/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs new file mode 100644 index 000000000..ff2a020be --- /dev/null +++ b/src/Orchard.Tests.Modules/Users/ShellSettingsUtility.cs @@ -0,0 +1,27 @@ +using System; +using System.Security.Cryptography; +using Orchard.Environment.Configuration; +using Orchard.Utility.Extensions; + +namespace Orchard.Tests.Modules.Users { + public class ShellSettingsUtility { + public static ShellSettings CreateEncryptionEnabled() { + // generate random keys for encryption + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + return new ShellSettings { + Name = "Alpha", + RequestUrlHost = "wiki.example.com", + RequestUrlPrefix = "~/foo", + EncryptionAlgorithm = "AES", + EncryptionKey = key.ToHexString(), + EncryptionIV = iv.ToHexString() + }; + } + } +} diff --git a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs index e50dd1567..6f1d02806 100644 --- a/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs +++ b/src/Orchard.Tests/Environment/Configuration/DefaultTenantManagerTests.cs @@ -58,7 +58,7 @@ namespace Orchard.Tests.Environment.Configuration { _appDataFolder.CreateFile("Sites\\Default\\Settings.txt", "Name: Default\r\nDataProvider: SqlCe\r\nDataConnectionString: something else"); IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock().Object); - var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux" }; + var foo = new ShellSettings {Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux"}; Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); loader.SaveSettings(foo); @@ -69,5 +69,19 @@ namespace Orchard.Tests.Environment.Configuration { Assert.That(text, Is.StringContaining("Bar")); Assert.That(text, Is.StringContaining("Quux")); } + + [Test] + public void EncryptionSettingsAreStoredAndReadable() { + IShellSettingsManager loader = new ShellSettingsManager(_appDataFolder, new Mock().Object); + var foo = new ShellSettings { Name = "Foo", DataProvider = "Bar", DataConnectionString = "Quux", EncryptionAlgorithm = "AES", EncryptionKey = "ABCDEFG", EncryptionIV= "HIJKL" }; + loader.SaveSettings(foo); + Assert.That(loader.LoadSettings().Count(), Is.EqualTo(1)); + + var settings = loader.LoadSettings().First(); + + Assert.That(settings.EncryptionAlgorithm, Is.EqualTo("AES")); + Assert.That(settings.EncryptionKey, Is.EqualTo("ABCDEFG")); + Assert.That(settings.EncryptionIV, Is.EqualTo("HIJKL")); + } } } diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index c1537c8af..0fcaf7102 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -245,6 +245,7 @@ + diff --git a/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs b/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs new file mode 100644 index 000000000..37a9fbc45 --- /dev/null +++ b/src/Orchard.Tests/Security/DefaultEncryptionServiceTests.cs @@ -0,0 +1,53 @@ +using System.Security.Cryptography; +using System.Text; +using Autofac; +using NUnit.Framework; +using Orchard.Environment.Configuration; +using Orchard.Security; +using Orchard.Security.Providers; +using Orchard.Utility.Extensions; + +namespace Orchard.Tests.Security { + [TestFixture] + public class DefaultEncryptionServiceTests { + private IContainer container; + + [SetUp] + public void Init() { + + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + var shellSettings = new ShellSettings { + Name = "Foo", + DataProvider = "Bar", + DataConnectionString = "Quux", + EncryptionAlgorithm = "AES", + EncryptionKey = key.ToHexString(), + EncryptionIV = iv.ToHexString() + }; + + var builder = new ContainerBuilder(); + builder.RegisterInstance(shellSettings); + builder.RegisterType().As(); + container = builder.Build(); + } + + [Test] + public void CanEncodeAndDecodeData() { + var encryptionService = container.Resolve(); + + var secretData = Encoding.Unicode.GetBytes("this is secret data"); + var encrypted = encryptionService.Encode(secretData); + var decrypted = encryptionService.Decode(encrypted); + + Assert.That(encrypted, Is.Not.EqualTo(decrypted)); + Assert.That(decrypted, Is.EqualTo(secretData)); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs index d00d99d7b..6fdb834a1 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs +++ b/src/Orchard.Web/Modules/Orchard.ContentTypes/Services/ContentDefinitionService.cs @@ -6,10 +6,8 @@ using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData.Models; -using Orchard.ContentTypes.Extensions; using Orchard.ContentTypes.ViewModels; using Orchard.Core.Contents.Extensions; -using Orchard.Core.Contents.Settings; using Orchard.Localization; namespace Orchard.ContentTypes.Services { diff --git a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs index dda95b909..c7a2e1e20 100644 --- a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs @@ -1,14 +1,27 @@ -using JetBrains.Annotations; +using System; +using System.Text; +using JetBrains.Annotations; using Orchard.Email.Models; using Orchard.Data; using Orchard.ContentManagement.Handlers; +using Orchard.Security; namespace Orchard.Email.Handlers { [UsedImplicitly] public class SmtpSettingsPartHandler : ContentHandler { - public SmtpSettingsPartHandler(IRepository repository) { + private readonly IEncryptionService _encryptionService; + + public SmtpSettingsPartHandler(IRepository repository, IEncryptionService encryptionService) { + _encryptionService = encryptionService; Filters.Add(new ActivatingFilter("Site")); Filters.Add(StorageFilter.For(repository)); + + OnLoaded(LazyLoadHandlers); + } + + void LazyLoadHandlers(LoadContentContext context, SmtpSettingsPart part) { + part.PasswordField.Getter(() => String.IsNullOrWhiteSpace(part.Record.Password) ? String.Empty : Encoding.UTF8.GetString(_encryptionService.Decode(Convert.FromBase64String(part.Record.Password)))); + part.PasswordField.Setter(value => part.Record.Password = String.IsNullOrWhiteSpace(value) ? String.Empty : Convert.ToBase64String(_encryptionService.Encode(Encoding.UTF8.GetBytes(value)))); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs index 964d022df..e371459b4 100644 --- a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs +++ b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs @@ -1,14 +1,13 @@ -using System.Text; -using System.Web.Security; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using System; +using Orchard.ContentManagement.Utilities; namespace Orchard.Email.Models { public class SmtpSettingsPart : ContentPart { - public bool IsValid() { - return !String.IsNullOrWhiteSpace(Record.Host) - && Record.Port > 0 - && !String.IsNullOrWhiteSpace(Record.Address); + private readonly ComputedField _password = new ComputedField(); + + public ComputedField PasswordField { + get { return _password; } } public string Address { @@ -42,8 +41,14 @@ namespace Orchard.Email.Models { } public string Password { - get { return String.IsNullOrWhiteSpace(Record.Password) ? String.Empty : Encoding.UTF8.GetString(MachineKey.Decode(Record.Password, MachineKeyProtection.All)); ; } - set { Record.Password = String.IsNullOrWhiteSpace(value) ? String.Empty : MachineKey.Encode(Encoding.UTF8.GetBytes(value), MachineKeyProtection.All); } + get { return _password.Value; } + set { _password.Value = value; } + } + + public bool IsValid() { + return !String.IsNullOrWhiteSpace(Record.Host) + && Record.Port > 0 + && !String.IsNullOrWhiteSpace(Record.Address); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs index 4c68417fc..2d26c4bbb 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/Services/SetupService.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Linq; +using System.Security.Cryptography; using System.Web; using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData; @@ -29,6 +30,7 @@ using Orchard.Settings; using Orchard.Environment.State; using Orchard.Data.Migration; using Orchard.Themes.Services; +using Orchard.Utility.Extensions; using Orchard.Widgets.Models; using Orchard.Widgets; @@ -118,6 +120,21 @@ namespace Orchard.Setup.Services { shellSettings.DataTablePrefix = context.DatabaseTablePrefix; } + #region Encryption Settings + + // generate random keys for encryption + var key = new byte[32]; + var iv = new byte[16]; + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(key); + random.GetBytes(iv); + } + + shellSettings.EncryptionAlgorithm = "AES"; + shellSettings.EncryptionKey = key.ToHexString(); + shellSettings.EncryptionIV = iv.ToHexString(); + #endregion + var shellDescriptor = new ShellDescriptor { Features = context.EnabledFeatures.Select(name => new ShellFeature { Name = name }) }; diff --git a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs index a1ed2f3fc..712936452 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Services/UserService.cs @@ -25,13 +25,15 @@ namespace Orchard.Users.Services { private readonly IClock _clock; private readonly IMessageManager _messageManager; private readonly ShellSettings _shellSettings; + private readonly IEncryptionService _encryptionService; - public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings) { + public UserService(IContentManager contentManager, IMembershipService membershipService, IClock clock, IMessageManager messageManager, ShellSettings shellSettings, IEncryptionService encryptionService) { _contentManager = contentManager; _membershipService = membershipService; _clock = clock; _messageManager = messageManager; _shellSettings = shellSettings; + _encryptionService = encryptionService; Logger = NullLogger.Instance; } @@ -66,24 +68,22 @@ namespace Orchard.Users.Services { } public string CreateNonce(IUser user, TimeSpan delay) { - // the tenant's name is added to the token to prevent cross-tenant requests - var challengeToken = new XElement("n", new XAttribute("s", _shellSettings.Name), new XAttribute("un", user.UserName), new XAttribute("utc", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString(); - var data = Encoding.Unicode.GetBytes(challengeToken); - return MachineKey.Encode(data, MachineKeyProtection.All); + var challengeToken = new XElement("n", new XAttribute("un", user.UserName), new XAttribute("utc", _clock.UtcNow.ToUniversalTime().Add(delay).ToString(CultureInfo.InvariantCulture))).ToString(); + var data = Encoding.UTF8.GetBytes(challengeToken); + return Convert.ToBase64String(_encryptionService.Encode(data)); } - public bool DecryptNonce(string challengeToken, out string username, out DateTime validateByUtc) { + public bool DecryptNonce(string nonce, out string username, out DateTime validateByUtc) { username = null; validateByUtc = _clock.UtcNow; try { - var data = MachineKey.Decode(challengeToken, MachineKeyProtection.All); - var xml = Encoding.Unicode.GetString(data); + var data = _encryptionService.Decode(Convert.FromBase64String(nonce)); + var xml = Encoding.UTF8.GetString(data); var element = XElement.Parse(xml); - var tenant = element.Attribute("s").Value; username = element.Attribute("un").Value; validateByUtc = DateTime.Parse(element.Attribute("utc").Value, CultureInfo.InvariantCulture); - return String.Equals(_shellSettings.Name, tenant, StringComparison.Ordinal) && _clock.UtcNow <= validateByUtc; + return _clock.UtcNow <= validateByUtc; } catch { return false; diff --git a/src/Orchard/ContentManagement/Utilities/ComputedField.cs b/src/Orchard/ContentManagement/Utilities/ComputedField.cs new file mode 100644 index 000000000..3bbd33b7a --- /dev/null +++ b/src/Orchard/ContentManagement/Utilities/ComputedField.cs @@ -0,0 +1,29 @@ +using System; + +namespace Orchard.ContentManagement.Utilities { + public class ComputedField { + private Func _getter; + private Action _setter; + + public T Value { + get { return GetValue(); } + set { SetValue(value); } + } + + public void Getter(Func loader) { + _getter = loader; + } + + public void Setter(Action setter) { + _setter = setter; + } + + private T GetValue() { + return _getter(); + } + + private void SetValue(T value) { + _setter(value); + } + } +} diff --git a/src/Orchard/Environment/Configuration/ShellSettings.cs b/src/Orchard/Environment/Configuration/ShellSettings.cs index c6717938f..65795783f 100644 --- a/src/Orchard/Environment/Configuration/ShellSettings.cs +++ b/src/Orchard/Environment/Configuration/ShellSettings.cs @@ -16,6 +16,9 @@ DataTablePrefix = settings.DataTablePrefix; RequestUrlHost = settings.RequestUrlHost; RequestUrlPrefix = settings.RequestUrlPrefix; + EncryptionAlgorithm = settings.EncryptionAlgorithm; + EncryptionKey = settings.EncryptionKey; + EncryptionIV = settings.EncryptionIV; State = settings.State; } @@ -28,6 +31,10 @@ public string RequestUrlHost { get; set; } public string RequestUrlPrefix { get; set; } + public string EncryptionAlgorithm { get; set; } + public string EncryptionKey { get; set; } + public string EncryptionIV { get; set; } + public TenantState State { get; set; } } } diff --git a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs index 9e819b36e..6e6d24044 100644 --- a/src/Orchard/Environment/Configuration/ShellSettingsManager.cs +++ b/src/Orchard/Environment/Configuration/ShellSettingsManager.cs @@ -84,6 +84,15 @@ namespace Orchard.Environment.Configuration { case "RequestUrlPrefix": shellSettings.RequestUrlPrefix = settingFields[1]; break; + case "EncryptionAlgorithm": + shellSettings.EncryptionAlgorithm = settingFields[1]; + break; + case "EncryptionKey": + shellSettings.EncryptionKey = settingFields[1]; + break; + case "EncryptionIV": + shellSettings.EncryptionIV = settingFields[1]; + break; } } } @@ -94,14 +103,18 @@ namespace Orchard.Environment.Configuration { if (settings == null) return ""; - return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\n", + return string.Format("Name: {0}\r\nDataProvider: {1}\r\nDataConnectionString: {2}\r\nDataPrefix: {3}\r\nRequestUrlHost: {4}\r\nRequestUrlPrefix: {5}\r\nState: {6}\r\nEncryptionAlgorithm: {7}\r\nEncryptionKey: {8}\r\nEncryptionIV: {9}\r\n", settings.Name, settings.DataProvider, settings.DataConnectionString ?? "null", settings.DataTablePrefix ?? "null", settings.RequestUrlHost ?? "null", settings.RequestUrlPrefix ?? "null", - settings.State != null ? settings.State.ToString() : String.Empty); + settings.State != null ? settings.State.ToString() : String.Empty, + settings.EncryptionAlgorithm ?? "null", + settings.EncryptionKey ?? "null", + settings.EncryptionIV ?? "null" + ); } } } diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index d89393040..a8350ed09 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -142,6 +142,7 @@ + @@ -171,7 +172,9 @@ + + diff --git a/src/Orchard/Security/IEncryptionService.cs b/src/Orchard/Security/IEncryptionService.cs new file mode 100644 index 000000000..e2d0471ce --- /dev/null +++ b/src/Orchard/Security/IEncryptionService.cs @@ -0,0 +1,20 @@ +namespace Orchard.Security { + /// + /// Provides encryption services adapted to securing tenant level information + /// + public interface IEncryptionService : ISingletonDependency { + /// + /// Decodes data that has been encrypted. + /// + /// The encrypted data to decrypt. + /// A Byte[] array that represents the decrypted data. + byte[] Decode(byte[] encodedData); + + /// + /// Encrypts data. + /// + /// The data to encrypt. + /// The encrypted value. + byte[] Encode(byte[] data); + } +} diff --git a/src/Orchard/Security/Providers/DefaultEncryptionService.cs b/src/Orchard/Security/Providers/DefaultEncryptionService.cs new file mode 100644 index 000000000..9620f4157 --- /dev/null +++ b/src/Orchard/Security/Providers/DefaultEncryptionService.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Orchard.Environment.Configuration; +using Orchard.Utility.Extensions; + +namespace Orchard.Security.Providers { + public class DefaultEncryptionService : IEncryptionService { + private readonly ShellSettings _shellSettings; + private const int SaltSize = 16; + + public DefaultEncryptionService(ShellSettings shellSettings ) { + _shellSettings = shellSettings; + } + + public byte[] Decode(byte[] encodedData) { + using ( var ms = new MemoryStream() ) { + using (var algorithm = CreateAlgorithm()) { + using ( var cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write) ) { + cs.Write(encodedData, 0, encodedData.Length); + cs.FlushFinalBlock(); + } + + // remove the salt part + return ms.ToArray().Skip(SaltSize).ToArray(); + } + } + } + + public byte[] Encode(byte[] data) { + var salt = new byte[SaltSize]; + + // generate a random salt to happend to encoded data + using ( var random = new RNGCryptoServiceProvider() ) { + random.GetBytes(salt); + } + using ( var ms = new MemoryStream() ) { + using (var algorithm = CreateAlgorithm()) { + using ( var cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write) ) { + // append the salt to the data and encrypt + var salted = salt.Concat(data).ToArray(); + cs.Write(salted, 0, salted.Length); + cs.FlushFinalBlock(); + } + + return ms.ToArray(); + } + } + } + + private SymmetricAlgorithm CreateAlgorithm() { + var encryptionAlgorithm = SymmetricAlgorithm.Create(_shellSettings.EncryptionAlgorithm); + + encryptionAlgorithm.Key = _shellSettings.EncryptionKey.ToByteArray(); + encryptionAlgorithm.IV = _shellSettings.EncryptionIV.ToByteArray(); + + return encryptionAlgorithm; + + } + } +} diff --git a/src/Orchard/Utility/Extensions/StringExtensions.cs b/src/Orchard/Utility/Extensions/StringExtensions.cs index bc1225b1e..3a11a1c65 100644 --- a/src/Orchard/Utility/Extensions/StringExtensions.cs +++ b/src/Orchard/Utility/Extensions/StringExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Text.RegularExpressions; using Orchard.Localization; @@ -55,5 +56,16 @@ namespace Orchard.Utility.Extensions { ? "" : Regex.Replace(text, @"(\r?\n)", replacement, RegexOptions.Singleline); } + + public static string ToHexString(this byte[] bytes) { + return BitConverter.ToString(bytes).Replace("-", ""); + } + + public static byte[] ToByteArray(this string hex) { + return Enumerable.Range(0, hex.Length). + Where(x => 0 == x % 2). + Select(x => Convert.ToByte(hex.Substring(x, 2), 16)). + ToArray(); + } } } \ No newline at end of file