Rhino Service BusField Level Security
One of the requirements that came up on my current project was the need to secure specific fields in a message during transit. I thought about it a while before I decided that this is something that should be made explicit in the message contract.
Here is an example from the tests:
1: public class ClassWithSecretField2: {
3: public WireEcryptedString ShouldBeEncrypted4: {
5: get; set;
6: }
7: }
WireEncryptedString is a type that would be encrypted on the wire, as the name suggest.
And defining the keys in the configuration is done in this way:
1: <facility id="rhino.esb" >2: <bus threadCount="1"3: numberOfRetries="5"4: endpoint="msmq://localhost/test_queue2"5: />6: <messages>7: <add name="Rhino.ServiceBus.Tests"8: endpoint="msmq://localhost/test_queue"/>9: <add name="Rhino.ServiceBus.Tests"10: endpoint="msmq://localhost/test_queue2"/>11: </messages>12: <security>13: <key>f/gdDWbDqHRvpqdRbTs3mxhGdZh9qCaDrasxJGXl+5s=</key>14: </security>15: </facility>
On the wire, it has the following format:
1: <?xml version='1.0' encoding='utf-8'?>2: <esb:messages3: xmlns:esb='http://servicebus.hibernatingrhinos.com/2008/12/20/esb'4: xmlns:tests.classwithsecretfield='Rhino.ServiceBus.Tests.When_Security_Is_Specified_In_Config+ClassWithSecretField, Rhino.ServiceBus.Tests'5: xmlns:datastructures.wireecryptedstring='Rhino.ServiceBus.DataStructures.WireEcryptedString, Rhino.ServiceBus' xmlns:string='string'>6: <tests.classwithsecretfield:ClassWithSecretField>7: <datastructures.wireecryptedstring:ShouldBeEncrypted>8: <string:Value iv='0yL9+t0uyDy9NeP7CU1Wow=='>q9a10IFuRxrzFoZewfdOyg==</string:Value>9: </datastructures.wireecryptedstring:ShouldBeEncrypted>10: </tests.classwithsecretfield:ClassWithSecretField>11: </esb:messages>
Following the Rhino Service Bus philosophy, it is quite a neat solution.
The actual encryption is doing using 256 bits key with Rijndael (AES). I considered other approaches, but all of them had quite a big overhead from manageability perspective.
There are some interesting implications for the implementation, that deserve some discussion. Let us assume that you send such a message to another end point.
If the endpoint…
- has the same key as us, the message will be decrypted and everything works.
- doesn’t have any security defined. At that point, the message will successfully deserialize. Any WireEncryptedString field will contain the encrypted value.
- has a different key defined. Message serialization will fail.
Trying to send a message that contains WireEncryptedString will throw, we do not allow such an action.
And now you can tell me how many holes there are in my system :-)
More posts in "Rhino Service Bus" series:
- (08 Aug 2009) DHT Saga Sate Persisters Options
- (21 Jan 2009) Concurrency Violations are Business Logic
- (19 Jan 2009) Concurrency in a distributed world
- (16 Jan 2009) Saga and State
- (15 Jan 2009) Field Level Security
- (14 Jan 2009) Understanding a Distributed System
- (14 Jan 2009) The Starbucks example
- (14 Jan 2009) Locality and Independence
- (14 Jan 2009) Managing Timeouts
Comments
It is nice to put things like encryption keys in the Windsor configuration because you can use properties and includes to externalize the actual value. Depending on your deployment scenario, you do things like store the keys on one machine and use includes to retrieve the properties config file via a share to make sure the keys are only defined in one place and not checked into source control.
Assuming two things 1) you don't want your production keys in source control 2) you want developers to be able to check the project code out of source control without having to make changes to app.confg files -- how would you handle having a different encryption key for development and production? I had to add some functionality to Windsor's include ability to get this working friction free for a similar task.
So you end up with code like:
classWithSecretField.ShouldBeEncrypted = new AESWireEncryptedMessage("mysecret");
Is that more desireable than:
1: public class ClassWithSecretField
2: {
3: public String ShouldBeEncrypted
4: {
5: get; set;
6: }
7: }
This configuration is part of the administration configuration, as such, it is handled like most configuration.
In most places, not only you don't have the same configuration between both places, but doing so would give you a big trouble.
The ideal way of handling this scenario is to have a dev.config file that is NOT checked into source control, which is created as part of the dev build process.
mikev,
checking custom attributes is very expensive.
Checking types is very cheap.
Well, our build tools remove the app|web.config from the release setup. The app|web.config is renamed to app|web.config.example and certain fields are replace with emptied. This happens for connection strings (remember, there is no database), file locations, (security) keys, etc.
Like the config tool of the 'enterprise library' we also provide our own configuration tool which helps the person who has to install the (web) application. Al our applications and extensions validate their config file (section) and applications will throw a 'fatal' configuration exception, where extensions won't initialize and only log their exception.
All our desktop-and applications follow the IoC and MVP/MVC design patterns. We have some moving parts, but every moving part is introduced as a service (facility). Depended modules always check if their dependencies are available and if not refuse to start. If any of the required services if not loaded, the application refuses to start.
There are obviously implications with storing encryption keys in plain text in a config file (if an attacker can get into your hosting environment then you're pretty shafted anyway, but hiding it from server admin staff is a useful precaution).
Perhaps define an interface with a single method (IEncryptionKeyRegistry.KeyFor(string keyName)?). You would then locate this in a separate (obfuscated) assembly. I've not looked into whether an obfuscator would make it impossible to identify encryption keys in the IL though.
Could the key change in certain circumstances? If so, the name of the key could be stored in the message (iv='0y..' keyName='key1'). The correct key could then be loaded from IEncryptionKeyRegistry, which would contain a history of key changes (in a dictionary of keys, keyed by their, er, key name).
Dan,
You can use DPAPI to encrypt sections of the configuration, and you wouldn't have to do a thing.
I explicitly do not want to add named keys for now.
It is likely something that I will have to support, but right at this moment, I want to avoid it.
It would add complexity to my situation.
Thoughts:
The serialization namespace should be a bit more descriptive and not simply 'string'
There should be an 'algo' attribute in the serialization for versioning
How does the deserializer truly 'know' that it decoded the string properly? Do you store a CRC or other hash inside of the value?
This should be promoted to a common library and not RSB-specific :)
1) while the serialization format is human readable, it is not intended to be really read by humans. It is intended to be high fidelity format for serializing objects.
2) more complexity that isn't needed at the moment. Adding configuration means adding more moving parts. I don't want moving parts.
3) That is handled internally by the encryption algo.
4) feel free to do so.
I am a bit confused with this post. I make the assumption that you are using the WireEncryptedString as a 'marker' type as it is cheaper than an Attribute? If this is the case then I understand where you are coming from but have the following reservation:
I need to have RSB at both ends (ie the publisher and subscriber to decrypt). Is this likely i.e. is this the design that it would not be interoperable with other subscribers?
Possibly confusing this with implementation detail but I would have thought you would have used the XML Encryption standards that exists already ( http://www.w3.org/TR/xmlenc-core/). This allows encryption of an XML document, XML element (and its children) or the character data within an element.
Cool thing about this is that RSB could raise a coarse grained message about a new "Customer" account with various levels of security on the one document. Depending on who you are you may or may not have the key to see data in the Account balance, contact details or location details. One message, many consumers, each getting a different view of the data.
Humble apologies if I am I completely confused.
Lee,
Yes, it is a marker type.
You don't need to have RSB at both ends, you just need the key. Since parsing this is very easy.
All the XML encryption stuff that I have seen were incredibly complicated, hard to implement and hard to manage.
I prefer to go with a bare bone solution that requires no thinking.
Comment preview