The Missing Piece: Encrypted Communications
In my last post (read it here) I wrote about an issue with security – that the actual communications between the client and the server were not secure. I also outlined the approach I was taking to remedy the issue.
Before I move onto my solution, I must mention that this is not a perfect solution. As one of my readers commented, this does not really cover a man-in-the-middle attack. It will prevent network sniffing and replay attacks, it is still possible to insert a listener between the client and the server.
It is possible to configure .NET Remoting to intrinsically secure communications – in which case there is no need to worry about this from the client side at all. However, it is not easy easy as it might appear, so I decided to finish my partial solution.
In time, I hope to be able to look at expanding the communications channels. Currently the only channel for communicating with the server is .NET Remoting – which was because of the way methods used to be called. Now with the new messaging-based framework, it is easier to add new channels. Hopefully I will get some time to look at using SslStream and NegotiateStream to secure communications.
First, how does the new code send encrypted messages to the server? This involved expanding how messages are currently sent.
Currently messaging uses the following model:
Using the new communications model, each client application will generate an instance of a CruiseServerClient. Actually they generate a CruiseServerClientBase instance, but the class that implements this abstract class for the new framework is CruiseServerClient. This class does not actually send any messages – instead it translates the method calls (e.g. ForceBuild) into messages (e.g. ProjectRequest), sends the message and then processes the response.
The actual sending is done by an instance of IServerConnection. This instance takes the specified messages and pushes them over the specified channel (either .NET Remoting or HTTP). Because the actual channel is encapsulated by an IServerConnection, it is easy to replace the channel with a different one.
For encrypted communications I added a new channel called EncryptingConnection. This channel is then inserted between the client and the underlying communications channel:
This means that the messages are encrypted (both ways), but the underlying transports (e.g. HTTP or .NET Remoting) can still be used without any changes.
There are a couple of extra changes. First, the original request message gets replaced by an EncryptedRequest message. This contains the original message, but encrypted (of course), and the original action that was requested. Secondly, the action has been changed to ProcessSecureRequest – more on this when I cover the server side changes.
EncryptingConnection is also responsible for the hand-shaking required to set up the encrypted communications. When a message is sent, the connection checks to see if it has a password. If it does not have a password, sets up a new common password by the following process:
- Ask the server for its public key
- Generate a new secret (using RijndaelManaged)
- Encrypt the secret using the server’s public key (using RSACryptoServiceProvider)
- Send the secret to the server and wait for the response
- Store the secret locally
Once this has been done, all messages are then encrypted using the common secret (again with RijndaelManaged). This includes both request and response messages.
The actual sending of the messages is then done by passing the message onto the underlying channel.
Now that the client can send messages, we need to look at the server side. After all, if the server can’t understand the messages, then nothing will be done!
All communications into the server come in via CruiseServerClient. This is a MarshalByRefObject-derived class that uses .NET Remoting. All HTTP communications actually go via the dashboard, which passes them onto the server using .NET Remoting. So this model simplifies things – I only need to change CruiseServerClient!
Currently CruiseServerClient has one overloaded method for processing messages – ProcessRequest(). This takes the incoming message, works out which method to call, converts the message into the required objects (if required) and then calls the method. It also handles generating the response and adding any error information.
To handle secure messages I have added a ProcessSecureRequest (). This method is called by ProcessRequest() and acts as a middle-man. Basically, when ProcessRequest() gets a secure message, it passes it onto ProcessSecureRequest(), which then works out which methods to call, converts the message, etc.
I have also added three other new methods:
- RetrievePublicKey() – this will send the server’s public key to the client. If the server does not have a public/private key pair it will generate the pair (using RSACryptoServiceProvider).
- InitialiseSecureConnection() – this receives the common secret from the client, decrypts it and stores it locally.
- TerminateSecureConnection() – this will remove the common secret for a client from the local store.
These methods are called as part of the hand-shaking between the client and the server.
Additionally, as part of these changes, I have added a new property to the messages called ChannelInformation. This property has information on how the message was transported – i.e. metadata about the message itself.
Currently, there is only one implementation – RemotingChannelSecurityInformation. This says the messages was transported using .NET Remoting. On this class is a property called IsEncrypted. ProcessRequest() generates an instance of this class and sets IsEncrypted to false, while ProcessSecureRequest() generates an instance with IsEncrypted set to true.
Turning On Encryption
Encryption is turned on from the client-side. When a client wants to communicate with a server using encrypted communications, they need to set UseEncryption to true in ClientStartUpSettings.
Currently, this is not possible with the dashboard, but I have added it to CCTray. When a new server connection is added (for either .NET Remoting or via the dashboard), there is a new option of turning on encryption:
To use encrypted communications, all that is needed is to check this box.
Unfortunately, due to the way CCTray is configured, it is not possible to turn on encryption for an existing server. Instead the server needs to be removed and re-added (yuck!) This is probably a good area for tidying up in a future release of CCTray.
All this work so far has been to set up encrypted communications. At this point, it is up to the client to decide whether the channel is encrypted or not – the server doesn’t really care. We need some way of telling the server to only accept encrypted communications (i.e. to enforce encryption).
To do this, I have added a new configuration option to the server security settings – channel. This is an optional setting and has one required property – the type of channel. The following shows how to configure a channel to use encrypted messages:
1: <internalSecurity>2: <channel type="encryptedChannel"/>3: <users>4: <!-- Omitted for brevity-->5: </users>6: <permissions>7: <!-- Omitted for brevity-->8: </permissions>9: </internalSecurity>
This tells the security manager it will only accept communications over an encrypted channel.
When a message is received by the message processer (i.e. after it has been received by CruiseServerClient, processed and passed on), it calls a method called ValidateRequest(). This does some simple validation checks (the message is less than a day old and it hasn’t been duplicated). I have expanded this validation check to detect a channel definition and if present to validate it.
The Basics Of Encryption
This provides some of the basic pieces for encrypted communications. It certainly is not a comprehensive solution – there are still some areas uncovered (like validating the server is who it says it is).
In terms of where this can go, there are the following items that can be included:
- Additional encryption mechanisms (e.g. DES, triple DES, AES, etc.)
- Additional security channels (e.g. SSL, negotiated)
- Validation of the server (perhaps via X.509 certificates)
- Use encryption from the dashboard
But this provides a start. Let me know what you think so far.