Granular Security with Windows Communication Foundation
Issues with network protocol security
Security can be applied via network protocols. For instance, IPSec can be applied at the IP layer of the TCP/IP stack. Also, at the application layer, SSL can be used.
Both of these mechanisms encrypt the entire communication, providing confidentiality and integrity.
However, this can present problems. To inspect firewall traffic, it is necessary to decrypt the communications at the firewall. It may be necessary to then encrypt it again before it reaches the destination server. Also, network monitoring of traffic can be problematic, as the communications cannot be read until they reach the destination. SSL and IPSec encrypt the entire communication, rather than just sections of it.
Solutions using WS-Security
WS-Security is particularly useful here, as it only encrypts message body, rather than the entire packet sent across the network.
Imagine a situation where details of an account holder are passed across the network. It would be prudent to encrypt data such as the credit card number. However, it would be useful to be able to monitor the name of the account holder, and not have their name encrypted.
Implementation using Windows Communication Foundation
How can we encrypt only the credit card number?
Conventionally, a DataContract is used to form the contract for the WCF communications.
[DataContract] public class CompositeType { bool boolValue = true; string stringValue = "Hello ";
[DataMember] public bool BoolValue { get { return boolValue; } set { boolValue = value; } }
[DataMember] public string StringValue { get { return stringValue; } set { stringValue = value; } } }
However, in this case, we're going to use the MessageContract, as it has an extra property within its attribute that we're going to use. The MessageContract is used when we need finer control over the shape of the SOAP message that we're sending.
[MessageContract] public class CompositeMessage { string _accountFirstName = string.Empty; string _accountSecondName = string.Empty; string _creditCardNumber = string.Empty;
[MessageHeader(ProtectionLevel = System.Net.Security.ProtectionLevel.None)] public string AccountFirstName { get { return _accountFirstName; } set { _accountFirstName = value; } }
[MessageHeader(ProtectionLevel = System.Net.Security.ProtectionLevel.None)] public string AcountSecondName { get { return _accountSecondName; } set { _accountSecondName = value; } }
[MessageBodyMember(ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign)] public string CreditCardNumber { get { return _creditCardNumber; } set { _creditCardNumber = value; } } }
This extra property is the ProtectionLevel. It has three possible values: None, where the value is sent in the clear. Sign, where a digital signature is applied. Finally, EncryptAndSign, where the value is encrypted as well as digitally signed.
We can also decide whether we want to place values in the SOAP header or the SOAP body.
In this case, we've used the MessageHeader attribute where we want the data in the SOAP header,and the MessageBody attribute where we need the value placed into the SOAP body. This is done to make it easier to examine the SOAP message later in the article.
We're placing the firstname and the secondname in the header, and leaving them unencrypted.
The credit card data is encrypted and placed in the body.
Here is the method on the service.
public CompositeMessage GetDataUsingMessage(CompositeMessage composite) {
composite.AccountFirstName += "Fred"; composite.AcountSecondName += "Bloggs"; composite.CreditCardNumber = "123456789";
return composite; }
On our data form, a button click event calls the service.
private void MessageContract_Click(object sender, EventArgs e) { ServiceReference1.Service1Client sv1 = new ServiceReference1.Service1Client();
string accountFirstName = string.Empty; string accountSecondName = string.Empty; string creditCardNumber = string.Empty;
sv1.GetDataUsingMessage(ref accountFirstName, ref accountSecondName, ref creditCardNumber); string display = "First name is " + accountFirstName + " second name is " + accountSecondName + " credit card number is " + creditCardNumber; MessageBox.Show(display); }
Notice that the properties of the MessageContract are passed by ref.
This is the configuration file for calling the service. Nothing special here - it was generated by Visual Studio.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IService1" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false"> <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" /> <security mode="Message"> <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" /> <message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="http://wibble/HostDemoService/service1.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1" name="WSHttpBinding_IService1"> <identity> <dns value="localhost" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
Here is the result.
In order to observe the actual SOAP communication, install Fiddler on your system. It can be downloaded from this address:
http://www.fiddlertool.com/fiddler/
This is an HTTPDebugging proxy that you can use to monitor traffic.
To make it easier to read, the data from the 'TextView' window has been placed into Explorer, with its automatic formatting of XML.
You can clearly see the AccountFirstName and AccountSecondName values have been placed in the header, and are in the clear (unencrypted).
What about the cerdit card number?
All we can see here is the CipherValue in the body, which contains the encrypted data.
How was it encrypted?
We've achieved our result, which was to send the first name and second name in the clear, and encrypt the credit card data.
But if we look at the config file from the last section, we can't see any certificates mentioned. So how was the data encrypted?
If you look back to the screen shot of Fiddler, you can see there were four communications captured (see the window on the left of the Fiddler screen). What's happened is that a key negotiation handshake was used to create a symmetric key for the communications.
You can use this key exchange method when communicating between Windows servers, giving you encryption without the bother of handing out certificates. This is not useful for digital signatures, but is valuable if confidentiality is your main concern.
Summary
We've found out how to encrypt just a part of the message sent, and take control of the SOAP that's sent when using WCF to communicate.
Also, we've found that we can encrypt communications between Windows servers without having to manage keys.
I hope this article gives you food for thought for protection of your communications. |