Skip to content

Commit da55d59

Browse files
committed
Encryption of lists and fields are now supported.
Closes #625
1 parent c4d7c58 commit da55d59

File tree

7 files changed

+309
-142
lines changed

7 files changed

+309
-142
lines changed

Samples/Encryption/Client/Client.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Client
66
{
7+
using System.Collections.Generic;
78
using NServiceBus.Encryption.Config;
89

910
public class EndpointConfig : IConfigureThisEndpoint, AsA_Client {}
@@ -12,8 +13,8 @@ public class SecurityConfig : IWantCustomInitialization
1213
{
1314
public void Init()
1415
{
15-
Configure.Instance.RijndaelEncryptionService()
16-
.DisableCompatibilityWithNSB2();//remove this line if you need to be compatible with a 2.X server
16+
Configure.Instance.RijndaelEncryptionService();
17+
//.DisableCompatibilityWithNSB2();//uncomment this line to turn off compatibility with2.X endpoints
1718
}
1819
}
1920

@@ -28,6 +29,11 @@ public void Run()
2829
{
2930
m.Secret = "betcha can't guess my secret";
3031
m.SubProperty = new MySecretSubProperty {Secret = "My sub secret"};
32+
m.CreditCards = new List<CreditCardDetails>
33+
{
34+
new CreditCardDetails{ValidTo = DateTime.UtcNow.AddYears(1), Number = "312312312312312"},
35+
new CreditCardDetails{ValidTo = DateTime.UtcNow.AddYears(2), Number = "543645546546456"}
36+
};
3137
});
3238
}
3339
}

Samples/Encryption/Messages/MessageWithSecretData.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
using NServiceBus;
22

33
namespace Messages
4-
{
4+
{
5+
using System;
6+
using System.Collections.Generic;
7+
58
public class MessageWithSecretData : IMessage
69
{
710
public WireEncryptedString Secret { get; set; }
811
public MySecretSubProperty SubProperty{ get; set; }
9-
12+
public List<CreditCardDetails> CreditCards { get; set; }
13+
}
14+
15+
public class CreditCardDetails
16+
{
17+
public DateTime ValidTo { get; set; }
18+
public WireEncryptedString Number { get; set; }
1019
}
1120

1221
public class MySecretSubProperty

Samples/Encryption/Server/Server.cs

+32-24
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
1-
using System;
2-
using Messages;
3-
using NServiceBus;
4-
5-
namespace Server
6-
{
7-
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
8-
{
9-
public void Init()
10-
{
11-
Configure.With()
12-
.StructureMapBuilder()
13-
.RijndaelEncryptionService();
14-
}
15-
}
16-
17-
public class Handler : IHandleMessages<MessageWithSecretData>
18-
{
19-
public void Handle(MessageWithSecretData message)
20-
{
21-
Console.WriteLine("I know your secret - it's '" + message.Secret + "'. And the subsecret is: " + message.SubProperty.Secret );
22-
}
23-
}
24-
}
1+
using System;
2+
using Messages;
3+
using NServiceBus;
4+
5+
namespace Server
6+
{
7+
public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
8+
{
9+
public void Init()
10+
{
11+
Configure.With()
12+
.StructureMapBuilder()
13+
.RijndaelEncryptionService();
14+
}
15+
}
16+
17+
public class Handler : IHandleMessages<MessageWithSecretData>
18+
{
19+
public void Handle(MessageWithSecretData message)
20+
{
21+
Console.Out.WriteLine("I know your secret - it's '" + message.Secret + "'");
22+
23+
if (message.SubProperty != null)
24+
Console.Out.WriteLine("SubSecret: " + message.SubProperty.Secret);
25+
26+
27+
if (message.CreditCards != null)
28+
foreach (var creditCard in message.CreditCards)
29+
Console.Out.WriteLine("CreditCard: {0} is valid to {1}", creditCard.Number.Value, creditCard.ValidTo);
30+
}
31+
}
32+
}

src/encryption/NServiceBus.Encryption/EncryptionMessageMutator.cs

+94-59
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace NServiceBus.Encryption
77
{
8+
using System.Collections;
89
using System.Collections.Concurrent;
910
using System.Collections.Generic;
1011
using System.Reflection;
@@ -19,45 +20,74 @@ public class EncryptionMessageMutator : IMessageMutator
1920

2021
public object MutateOutgoing(object message)
2122
{
22-
EncryptObject(message);
23+
ForEachMember(message, EncryptMember, IsEncryptedMember);
24+
2325
return message;
2426
}
2527

26-
void EncryptObject(object target)
28+
29+
public object MutateIncoming(object message)
2730
{
28-
var properties = GetAllProperties(target);
31+
ForEachMember(message, DecryptMember, IsEncryptedMember);
32+
return message;
33+
}
34+
35+
bool IsEncryptedMember(MemberInfo arg)
36+
{
37+
if (arg is PropertyInfo)
38+
return ((PropertyInfo)arg).IsEncryptedProperty();
39+
40+
if (arg is FieldInfo)
41+
return ((FieldInfo)arg).FieldType == typeof(WireEncryptedString);
42+
43+
return false;
2944

30-
foreach (var property in properties)
45+
}
46+
void ForEachMember(object root, Action<object, MemberInfo> action, Func<MemberInfo, bool> appliesTo)
47+
{
48+
if (root == null || visitedMembers.Contains(root))
49+
return;
50+
51+
visitedMembers.Add(root);
52+
53+
var members = GetFieldsAndProperties(root);
54+
55+
foreach (var member in members)
3156
{
32-
if (property.IsEncryptedProperty())
33-
{
34-
EncryptProperty(target, property);
35-
continue;
36-
}
3757

38-
if (property.PropertyType.IsPrimitive || IsSystemType(property.PropertyType))
58+
if (appliesTo(member))
59+
action(root, member);
60+
61+
//don't recurse over primitives and system types
62+
if (member.ReflectedType.IsPrimitive || member.ReflectedType.IsSystemType())
3963
continue;
4064

41-
//recurse
42-
EncryptObject(property.GetValue(target, null));
65+
var child = member.GetValue(root);
66+
67+
if (child is IEnumerable)
68+
foreach (var item in (IEnumerable)child)
69+
ForEachMember(item, action, appliesTo);
70+
else
71+
ForEachMember(child, action, appliesTo);
4372
}
4473
}
4574

46-
void EncryptProperty(object target, PropertyInfo encryptedProperty)
75+
76+
void EncryptMember(object target, MemberInfo member)
4777
{
48-
var valueToEncrypt = encryptedProperty.GetValue(target, null);
78+
var valueToEncrypt = member.GetValue(target);
4979

5080
if (valueToEncrypt == null)
5181
return;
5282

5383
if (EncryptionService == null)
5484
throw new InvalidOperationException(
5585
String.Format("Cannot encrypt field {0} because no encryption service was configured.",
56-
encryptedProperty.Name));
86+
member.Name));
5787

5888
if (valueToEncrypt is WireEncryptedString)
5989
{
60-
var encryptedString = (WireEncryptedString) valueToEncrypt;
90+
var encryptedString = (WireEncryptedString)valueToEncrypt;
6191
EncryptWireEncryptedString(encryptedString);
6292

6393
if (!ConfigureEncryption.EnsureCompatibilityWithNSB2)
@@ -69,50 +99,17 @@ void EncryptProperty(object target, PropertyInfo encryptedProperty)
6999
}
70100
else
71101
{
72-
encryptedProperty.SetValue(target, EncryptUserSpecifiedProperty(valueToEncrypt), null);
102+
member.SetValue(target, EncryptUserSpecifiedProperty(valueToEncrypt));
73103
}
74104

75-
Log.Debug(encryptedProperty.Name + " encrypted successfully");
76-
}
77-
78-
79-
public object MutateIncoming(object message)
80-
{
81-
DecryptObject(message);
82-
return message;
105+
Log.Debug(member.Name + " encrypted successfully");
83106
}
84107

85-
void DecryptObject(object target)
86-
{
87-
var properties = GetAllProperties(target);
88-
89-
foreach (var property in properties)
90-
{
91-
if (property.IsEncryptedProperty())
92-
{
93-
DecryptProperty(target, property);
94-
continue;
95-
}
96-
97-
if (property.PropertyType.IsPrimitive || IsSystemType(property.PropertyType))
98-
continue;
99-
100-
//recurse
101-
DecryptObject(property.GetValue(target, null));
102-
}
103-
}
104108

105-
bool IsSystemType(Type propertyType)
109+
void DecryptMember(object target, MemberInfo property)
106110
{
107-
var nameOfContainingAssembly = propertyType.Assembly.FullName.ToLower();
108111

109-
return nameOfContainingAssembly.StartsWith("mscorlib") || nameOfContainingAssembly.StartsWith("system.core");
110-
}
111-
112-
void DecryptProperty(object target, PropertyInfo property)
113-
{
114-
115-
var encryptedValue = property.GetValue(target, null);
112+
var encryptedValue = property.GetValue(target);
116113

117114
if (encryptedValue == null)
118115
return;
@@ -122,10 +119,10 @@ void DecryptProperty(object target, PropertyInfo property)
122119
String.Format("Cannot decrypt field {0} because no encryption service was configured.", property.Name));
123120

124121
if (encryptedValue is WireEncryptedString)
125-
Decrypt((WireEncryptedString) encryptedValue);
122+
Decrypt((WireEncryptedString)encryptedValue);
126123
else
127124
{
128-
property.SetValue(target, DecryptUserSpecifiedProperty(encryptedValue), null);
125+
property.SetValue(target, DecryptUserSpecifiedProperty(encryptedValue));
129126
}
130127

131128
Log.Debug(property.Name + " decrypted successfully");
@@ -149,6 +146,9 @@ string DecryptUserSpecifiedProperty(object encryptedValue)
149146

150147
void Decrypt(WireEncryptedString encryptedValue)
151148
{
149+
if (encryptedValue.EncryptedValue == null)
150+
throw new InvalidOperationException("Encrypted property is missing encryption data");
151+
152152
encryptedValue.Value = EncryptionService.Decrypt(encryptedValue.EncryptedValue);
153153
}
154154

@@ -170,21 +170,56 @@ void EncryptWireEncryptedString(WireEncryptedString wireEncryptedString)
170170
wireEncryptedString.Value = null;
171171

172172
}
173-
static IEnumerable<PropertyInfo> GetAllProperties(object target)
173+
static IEnumerable<MemberInfo> GetFieldsAndProperties(object target)
174174
{
175175
if (target == null)
176-
return new List<PropertyInfo>();
176+
return new List<MemberInfo>();
177177

178178
var messageType = target.GetType();
179179

180180
if (!cache.ContainsKey(messageType))
181-
cache[messageType] = messageType.GetProperties()
181+
cache[messageType] = messageType.GetMembers(BindingFlags.Public | BindingFlags.Instance)
182+
.Where(m => m is FieldInfo || m is PropertyInfo)
182183
.ToList();
183184

184185
return cache[messageType];
185186
}
186-
readonly static IDictionary<Type, IEnumerable<PropertyInfo>> cache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
187+
188+
readonly HashSet<object> visitedMembers = new HashSet<object>();
189+
190+
readonly static IDictionary<Type, IEnumerable<MemberInfo>> cache = new ConcurrentDictionary<Type, IEnumerable<MemberInfo>>();
187191

188192
readonly static ILog Log = LogManager.GetLogger(typeof(IEncryptionService));
189193
}
194+
195+
196+
public static class TypeExtensions
197+
{
198+
public static bool IsSystemType(this Type propertyType)
199+
{
200+
var nameOfContainingAssembly = propertyType.Assembly.FullName.ToLower();
201+
202+
return nameOfContainingAssembly.StartsWith("mscorlib") || nameOfContainingAssembly.StartsWith("system.core");
203+
}
204+
}
205+
206+
public static class MemberInfoExtensions
207+
{
208+
public static object GetValue(this MemberInfo member, object source)
209+
{
210+
if (member is FieldInfo)
211+
return ((FieldInfo)member).GetValue(source);
212+
213+
return ((PropertyInfo)member).GetValue(source, null);
214+
}
215+
216+
public static void SetValue(this MemberInfo member, object target, object value)
217+
{
218+
if (member is FieldInfo)
219+
((FieldInfo)member).SetValue(target, value);
220+
else
221+
((PropertyInfo)member).SetValue(target, value, null);
222+
}
223+
224+
}
190225
}

tests/encryption/NServiceBus.Encryption.Tests/ConventionBasedEncryptedStringSpecs.cs

+3-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public void Should_encrypt_the_value()
1515
};
1616
mutator.MutateOutgoing(message);
1717

18-
Assert.AreEqual(string.Format("{0}@{1}", "encrypted value", "initialization_vector"), message.EncryptedSecret);
18+
Assert.AreEqual(string.Format("{0}@{1}", "encrypted value", "init_vector"), message.EncryptedSecret);
1919
}
2020
}
2121
[TestFixture]
@@ -26,7 +26,7 @@ public void Should_encrypt_the_value()
2626
{
2727
var message = new ConventionBasedSecureMessage()
2828
{
29-
EncryptedSecret = "encrypted_value@init_vector"
29+
EncryptedSecret = "encrypted value@init_vector"
3030
};
3131
mutator.MutateIncoming(message);
3232

@@ -56,18 +56,11 @@ public void Should_throw_an_exception()
5656
}
5757

5858

59-
public class UserDefinedConventionContext
59+
public class UserDefinedConventionContext : WireEncryptedStringContext
6060
{
61-
protected EncryptionMessageMutator mutator;
62-
6361
[SetUp]
6462
public void SetUp()
6563
{
66-
mutator = new EncryptionMessageMutator
67-
{
68-
EncryptionService = new FakeEncryptionService("encrypted value")
69-
};
70-
7164
MessageConventionExtensions.IsEncryptedPropertyAction = (p) => p.Name.StartsWith("Encrypted");
7265
}
7366
}

0 commit comments

Comments
 (0)