Ayende @ Rahien

Refunds available at head office

Rhino Service Bus: Field 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 ClassWithSecretField
   2: {
   3:     public WireEcryptedString ShouldBeEncrypted
   4:     {
   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:messages 
   3:   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 :-)

Comments

Eric Hauser
01/15/2009 04:37 AM by
Eric Hauser

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.

mikev
01/15/2009 04:48 AM by
mikev

So you end up with code like:

classWithSecretField.ShouldBeEncrypted = new AESWireEncryptedMessage("mysecret");

Is that more desireable than:

1: public class ClassWithSecretField

2: {

        [Encrypted]

3: public String ShouldBeEncrypted

4: {

5: get; set;

6: }

7: }

Ayende Rahien
01/15/2009 05:42 AM by
Ayende Rahien

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.

Ayende Rahien
01/15/2009 05:43 AM by
Ayende Rahien

mikev,

checking custom attributes is very expensive.

Checking types is very cheap.

Dave
01/15/2009 08:13 AM by
Dave

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.

Dan Malcolm
01/15/2009 09:26 AM by
Dan Malcolm

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).

Ayende Rahien
01/15/2009 10:20 AM by
Ayende Rahien

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.

Anon
01/16/2009 12:00 PM by
Anon

Thoughts:

  1. The serialization namespace should be a bit more descriptive and not simply 'string'

  2. There should be an 'algo' attribute in the serialization for versioning

  3. How does the deserializer truly 'know' that it decoded the string properly? Do you store a CRC or other hash inside of the value?

  4. This should be promoted to a common library and not RSB-specific :)

Ayende Rahien
01/16/2009 02:26 PM by
Ayende Rahien

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.

Lee Campbell
01/16/2009 02:51 PM by
Lee Campbell

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.

Ayende Rahien
01/16/2009 02:55 PM by
Ayende Rahien

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.

Comments have been closed on this topic.