Skip to content

Commit

Permalink
Added key identifiers to prevent possible data corruption when decryp…
Browse files Browse the repository at this point in the history
…tion data.
  • Loading branch information
ramonsmits committed Oct 21, 2015
1 parent d28f3b4 commit 78cee67
Show file tree
Hide file tree
Showing 12 changed files with 726 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Logging;
using NServiceBus.Config;
using NServiceBus.Encryption;
using NServiceBus.Encryption.Rijndael;

namespace NServiceBus
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Config;
using Encryption.Rijndael;
using Common.Logging;
using NServiceBus.ObjectBuilder;

/// <summary>
/// Contains extension methods to NServiceBus.Configure.
/// </summary>
Expand All @@ -17,58 +17,133 @@ public static class ConfigureRijndaelEncryptionService
/// <summary>
/// Use 256 bit AES encryption based on the Rijndael cipher.
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static Configure RijndaelEncryptionService(this Configure config)
{
var section = Configure.GetConfigSection<RijndaelEncryptionServiceConfig>();

if (section == null)
Logger.Warn("Could not find configuration section for Rijndael Encryption Service.");

var encryptConfig = config.Configurer.ConfigureComponent<EncryptionService>(DependencyLifecycle.SingleInstance);
if (section != null)

if (section == null) throw new InvalidOperationException("No RijndaelEncryptionServiceConfig section present.");

if (string.IsNullOrWhiteSpace(section.Key))
{
throw new Exception("The RijndaelEncryptionServiceConfig has an empty 'Key' attribute.");
}

ValidateConfigSection(section);

byte[] encryptionKey = null;
var keys = ExtractKeysFromConfigSection(section);

if (string.IsNullOrEmpty(section.KeyIdentifier))
{
Logger.Error("No encryption key identifier configured. Messages with encrypted properties will fail to send. Please add an encryption key identifier to the rijndael encryption service configuration.");
}
else if (!keys.TryGetValue(section.KeyIdentifier, out encryptionKey))
{
throw new InvalidOperationException("No encryption key for given encryption key identifier.");
}
else
{
if (string.IsNullOrWhiteSpace(section.Key))
{
throw new Exception("The RijndaelEncryptionServiceConfig has an empty 'Key' attribute.");
}
encryptConfig.ConfigureProperty(s => s.Key, Encoding.ASCII.GetBytes(section.Key));
var expiredKeys = ExtractExpiredKeysFromConfigSection(section);
if (expiredKeys != null)
{
encryptConfig.ConfigureProperty(s => s.ExpiredKeys, expiredKeys.Select(x => Encoding.ASCII.GetBytes(x)).ToList());
}
// VerifyEncryptionKey(encryptionKey);
}

// ReSharper disable once RedundantTypeArgumentsOfMethod
config.Configurer.ConfigureComponent<EncryptionService>(DependencyLifecycle.SingleInstance)
.ConfigureProperty(p => p.EncryptionKeyIdentifier, section.KeyIdentifier)
.ConfigureProperty(p => p.Keys, keys)
.ConfigureProperty(p => p.Key, encryptionKey)
.ConfigureProperty(p => p.ExpiredKeys, ExtractDecryptionKeysFromConfigSection(section));

return config;
}

internal static List<string> ExtractExpiredKeysFromConfigSection(RijndaelEncryptionServiceConfig section)
internal static void ValidateConfigSection(RijndaelEncryptionServiceConfig section)
{
if (section == null)
{
throw new Exception("No RijndaelEncryptionServiceConfig defined. Please specify a valid 'RijndaelEncryptionServiceConfig' in your application's configuration file.");
}
if (section.ExpiredKeys == null)
{
return new List<string>();
throw new Exception("RijndaelEncryptionServiceConfig.ExpiredKeys is null.");
}
var encryptionKeys = section.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Select(x => x.Key)
.ToList();
if (encryptionKeys.Any(string.IsNullOrWhiteSpace))
if (string.IsNullOrWhiteSpace(section.Key))
{
throw new Exception("The RijndaelEncryptionServiceConfig has an empty 'Key' property.");
}
if (RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveWhiteSpace(section))
{
throw new Exception("The RijndaelEncryptionServiceConfig has a 'ExpiredKeys' property defined however some keys have no 'Key' property set.");
}
if (RijndaelEncryptionServiceConfigValidations.OneOrMoreExpiredKeysHaveNoKeyIdentifier(section))
{
throw new Exception("The RijndaelEncryptionServiceConfig has a 'ExpiredKeys' property defined however some keys have no data.");
Logger.Warn("The RijndaelEncryptionServiceConfig has a 'ExpiredKeys' property defined however some keys have no 'KeyIdentifier' property value. Please verify if this is intentional.");
}
if (encryptionKeys.Any(x => x == section.Key))
if (RijndaelEncryptionServiceConfigValidations.EncryptionKeyListedInExpiredKeys(section))
{
throw new Exception("The RijndaelEncryptionServiceConfig has a 'Key' that is also defined inside the 'ExpiredKeys'.");
}
if (RijndaelEncryptionServiceConfigValidations.ExpiredKeysHaveDuplicateKeys(section))
{
throw new Exception("The RijndaelEncryptionServiceConfig has ExpiredKeys defined with duplicate 'Key' properties.");
}
if (RijndaelEncryptionServiceConfigValidations.ConfigurationHasDuplicateKeyIdentifiers(section))
{
throw new Exception("The RijndaelEncryptionServiceConfig has duplicate KeyIdentifiers defined with the same key identifier. Key identifiers must be unique in the complete configuration section.");
}
}

internal static List<byte[]> ExtractDecryptionKeysFromConfigSection(RijndaelEncryptionServiceConfig section)
{
var o = new List<byte[]>();
o.Add(ParseKey(section.Key));
o.AddRange(section.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Select(x => ParseKey(x.Key)));

return o;
}

static byte[] ParseKey(string key)
{
return Encoding.ASCII.GetBytes(key);
}

internal static IDictionary<string, byte[]> ExtractKeysFromConfigSection(RijndaelEncryptionServiceConfig section)
{
var result = new Dictionary<string, byte[]>();

AddKeyIdentifierItems(section, result);

if (encryptionKeys.Count != encryptionKeys.Distinct().Count())
foreach (RijndaelExpiredKey item in section.ExpiredKeys)
{
throw new Exception("The RijndaelEncryptionServiceConfig has overlapping ExpiredKeys defined. Please ensure that no keys overlap in the 'ExpiredKeys' property.");
AddKeyIdentifierItems(item, result);
}

return result;
}

static void AddKeyIdentifierItems(RijndaelEncryptionServiceConfig item, Dictionary<string, byte[]> result)
{
if (!string.IsNullOrEmpty(item.KeyIdentifier))
{
var key = ParseKey(item.Key);
result.Add(item.KeyIdentifier, key);
}
return encryptionKeys;
}

static void AddKeyIdentifierItems(RijndaelExpiredKey item, Dictionary<string, byte[]> result)
{
if (!string.IsNullOrEmpty(item.KeyIdentifier))
{
var key = ParseKey(item.Key);
result.Add(item.KeyIdentifier, key);
}
}

private static readonly ILog Logger = LogManager.GetLogger(typeof(RijndaelEncryptionServiceConfig));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Compile Include="ConfigureRijndaelEncryptionService.cs" />
<Compile Include="RijndaelEncryptionServiceConfig.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RijndaelEncryptionServiceConfigValidations.cs" />
<Compile Include="RijndaelExpiredKey.cs" />
<Compile Include="RijndaelExpiredKeyCollection.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Configuration;
using System.Configuration;

namespace NServiceBus.Config
{
Expand All @@ -20,7 +20,6 @@ public string Key
this["Key"] = value;
}
}

/// <summary>
/// Contains the expired decryptions that are currently being phased out.
/// </summary>
Expand All @@ -36,5 +35,20 @@ public RijndaelExpiredKeyCollection ExpiredKeys
this["ExpiredKeys"] = value;
}
}
/// <summary>
/// The encryption key identfier used for decryption.
/// </summary>
[ConfigurationProperty("KeyIdentifier", IsRequired = false)]
public string KeyIdentifier
{
get
{
return this["KeyIdentifier"] as string;
}
set
{
this["KeyIdentifier"] = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace NServiceBus
{
using System.Linq;
using NServiceBus.Config;

internal static class RijndaelEncryptionServiceConfigValidations
{
public static bool ConfigurationHasDuplicateKeyIdentifiers(RijndaelEncryptionServiceConfig section)
{
// Combine all key identifier values, filter the empty ones, split them
return section
.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Select(x => x.KeyIdentifier)
.Union(new[] { section.KeyIdentifier })
.Where(x => !string.IsNullOrEmpty(x))
.Select(x => x.Split(';'))
.SelectMany(x => x)
.GroupBy(x => x)
.Any(x => x.Count() > 1);
}

public static bool ExpiredKeysHaveDuplicateKeys(RijndaelEncryptionServiceConfig section)
{
var items = section
.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.ToList();

return items.Count != items.Select(x => x.Key).Distinct().Count();
}

public static bool EncryptionKeyListedInExpiredKeys(RijndaelEncryptionServiceConfig section)
{
return section
.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Any(x => x.Key == section.Key);
}

public static bool OneOrMoreExpiredKeysHaveNoKeyIdentifier(RijndaelEncryptionServiceConfig section)
{
return section
.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Any(x => string.IsNullOrEmpty(x.KeyIdentifier));
}

public static bool ExpiredKeysHaveWhiteSpace(RijndaelEncryptionServiceConfig section)
{
return section
.ExpiredKeys
.Cast<RijndaelExpiredKey>()
.Any(x => string.IsNullOrWhiteSpace(x.Key));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System;
using System.Configuration;

namespace NServiceBus.Config
{

/// <summary>
/// A configuration element representing a Rijndael encryption key.
/// </summary>
public class RijndaelExpiredKey : ConfigurationElement, IComparable<RijndaelExpiredKey>
public class RijndaelExpiredKey : ConfigurationElement
{

/// <summary>
Expand All @@ -25,11 +25,20 @@ public string Key
}
}


int IComparable<RijndaelExpiredKey>.CompareTo(RijndaelExpiredKey other)
/// <summary>
/// The encryption key identfier used for decryption.
/// </summary>
[ConfigurationProperty("KeyIdentifier", IsRequired = false)]
public string KeyIdentifier
{
return String.Compare(Key, other.Key, StringComparison.Ordinal);
get
{
return this["KeyIdentifier"] as string;
}
set
{
this["KeyIdentifier"] = value;
}
}

}
}
}
Loading

0 comments on commit 78cee67

Please sign in to comment.