Click here to Skip to main content
Click here to Skip to main content

WCF Client Server Application with Custom Authentication, Authorization, Encryption and Compression - Part 1

, 22 Mar 2011
Rate this:
Please Sign up or sign in to vote.
HTTP - No IIS; Authentication - No SSL/X509 Certificate; Encryption - RSA+AES for Request, AES for Response; Compression - GZip for both Request/Response
Part 2, containing detailed explanations of the implementation can be found here.

Table of Contents

Introduction

With the appearance of Windows Communication Foundation, building Service Oriented Applications became easier than ever. And lots of articles poured in with extensions for special cases. Even so, there are still situations left untreated. Like the following I had to resolve:

  • Client-server application – http protocol – NO IIS
  • Authentication – user/password from a database – NO SSL/X509 certificate
  • Authorization – roles from a database
  • Encryption for the credentials (with option for the entire request/response)
  • Compression for both the request and response

Logical Solution

For each of the above requirements/restrictions, we have:

  • No IIS – We need our own http server – can be easily done by WCF in a few lines of code - no need to insist on this.
  • User/Password authentication – This is easily done by default, but an X509 certificate is required. Therefore we need our own mechanism: we’ll add the credentials to the header of the message and encrypt them (or the entire message).
  • Encryption – We’ll use an asymmetric algorithm[1] (RSA) with public/private keys. Usually, it is needed that the server and the client have their own set of public/private keys to encrypt both the request and the response, but we’ll use an artifice to avoid the client set of keys.

    With RSA, only a small amount of data can be encrypted (for 2048 bit encryption, only 128 bytes of data). Therefore it is used to encrypt a random generated password that will be used by a symmetric algorithm[2] (AES) to encrypt the message. The server decrypts with its private key, the password of the client, with that password decrypts the message. And using the same password encrypts the response message.

  • Additional security for the credentials – Even if the credentials are encrypted, a listener can get those encrypted credentials and launch a new request, therefore we’ll add an expiration date for the message.
    Also, during the time till expiration, the password is banned - it cannot be used by another message.
    This way, if a copy of the message is sent right away, it will be rejected because the password is banned; if it is sent after the password ban expired, then the authentication token expired also and the message will be rejected. Actually, the password will act like a nonce.
  • Compression – we’ll use gzip compression/decompression just before sending/receiving the message and the response.

Now, let’s see how the above generic considerations look in the client-server message flow:

  1. Server starts; a new RSA key is generated (or loaded from the disk).
  2. Client starts; it asks the server for the public key and time; based on server time and client time, it will calculate the client-server-timespan.
  3. Client prepares the request message
    1. The credentials are added to the message header; the Credentials token will have User/Password/Expires properties; the Expires will be calculated as client time + client-server-timespan + a few seconds;
    2. The message (or only the credentials part) is encrypted; more exactly:
      • A random key is generated and saved along with the message id;
      • The message (or the credentials part) is encrypted with the AES algorithm using the previous generated key;
      • The AES’ key is encrypted using the server’s RSA public key and added to the encrypted message
    3. The message is compressed
  4. Server receives the request message
    1. The message is decompressed
    2. The message is decrypted; more exactly:
      • The client AES’ key is retrieved by decrypting using the server’s private key;
      • If the key is in the ban list, a SecurityException is thrown; if it isn't, it is added with the ban's expiration date.
      • The message is decrypted using the client key;
      • The client’s key will be saved along with the message id – it will be needed to encrypt the response with the same id;
    3. The credentials are extracted from the message; the Expires is compared with server’s current time and if it is bigger, an AuthenticationException is thrown;
    4. Authentication – The credentials are verified against a database;
    5. Authorization – The roles of the authenticated user are retrieved from a database/cache.
  5. Server prepares the response message
    1. The message is encrypted; more exactly the message will be encrypted (AES) using the client’s key saved during decryption of the request;
    2. The message is compressed
  6. Client receives the response message
    1. The message is decompressed.
    2. The message is decrypted using the key saved during encryption (3.b.)

WCF Considerations

How to extend WCF to implement the above flow

On the client, for adding the credentials, we’ll use a BehaviorExtensionElement that implements IClientMessageInspector which has a BeforeSendRequest method (see implementation here).

On the server, for authentication, we need a class to implement IIdentity and a class derived from ServiceAuthenticationManager and to override the Authenticate method[3] (see implementation here).

For authorization, we need classes that implement IPrincipal and IAuthorizationPolicy respectively (see implementation here).

On both client and server, for cryptography and compression, we’ll need classes derived from MessageEncoder[4], MessageEncoderFactory[13], MessageEncodingBindingElement, BindingElementExtensionElement; the first class derived from MessageEncoder will do the actual encryption/decryption and compression/decompression; the class derived from BindingElementExtensionElement is our entry point from the config; it will use a MessageEncodingBindingElement that will use a MessageEncoderFactory that will use the MessageEncoder (see implementation here).
While compression is the same for the server and the client, the encryption differs on client from the server therefore we’ll have the above classes defined for each of them (of course, what is the same will be put in a parent class from which both the client and the server versions will derive.

You may think that a better solution would’ve been to use the message encoder only for the compression and to use a formatter to encrypt the message, but it won’t work: authentication happens before de-formatting on the server so we need to have the credentials decrypted by then.

Implementation

We’ll have 3 projects – Client, Server and Common (common library for both the server and the client).
And the following classes - all resulting naturally from the above arguments: Credentials, ServerInfo, a ClientCriptographer and a ServerCryptographer, a ClientEncoder and a ServerEncoder.
The following diagram contains the main classes:

(Click on the diagram to enlarge)

(Click on the diagram to enlarge)

Due to its size, the implementation will be presented in detail in Part 2 of this article.

Security Considerations

Trying to decipher the credentials, it’s complicated – the credentials token changes with every request as the Expires property changes – and more important, the AES key is changed with every request.

It would be easier to try to decipher server’s RSA key - but not easy enough. Everything is in fact reduced to the amount of the processing power that the cryptanalyst has at his disposal. Since he doesn’t have "the universal code breaker"[10] working for him, breaking the RSA key may take month, even years – but it is doable. However, this can be useless if the client credentials and the RSA key are changed often; (already the RSA key it is changed every time the server is restarted).

The real problem comes from an attacker that can control the conversation between client and server and can impersonate them; like the man in the middle attack[15]. To avoid this kind of attack, you need to use SSL that authenticates the server using a mutually trusted certification authority.

How to Use the Code

Just search the entire solution for "example"; there you can do your modifications/addings. Or, based on the example model, add your own service.
You may be also interested in changing the contentEncryption and contentCompression properties from the ClientMessageEncoding and ServerMeesageEncoding from the configuration files.

Extensions Ideas

Most of them are related to changing the encoding:

  • to allow encrypting only the response or only the request or only parts of them;
  • to allow compressing only the response or the request;
  • to change the text encoding to binary encoding (which means changing the XML encryption to a custom binary encryption);
  • to change the order of encryption and compression – encryption followed by compression is not always the best solution.

Notes

  1. The solution uses .NET 4.0.
  2. Please ignore the warnings related to the source control (TFS).
  3. You may need to re-add some of the references: System.Configuration, System.ServiceModel, System.IdentityModel, System.Security, System.Runtime.Serialization, Ionic.Zip (the last is in the Common project; the others come with .NET 4).
  4. The references are for both parts of the article and for the attached code (comments with numbers).
  5. You need administrative rights to start the server.
  6. To use Microsoft’s implementation of zip and remove the need for Ionic.Zip.dll, just change in the Common Project, Encoding.cs the “using Ionic.Zlib;” with “using System.IO.Compression;”; then you can delete the reference and the DLL.
  7. To avoid duplication, the code is attached only to the first part of the article.
  8. If you want to directly test the server.exe and the client.exe from the attached zip, you need to unblock first server.exe.config and client.exe.config (right click, properties, unblock); otherwise you'll get a configuration error.

References

[1] RSACryptoServiceProvider Info
[2] AesCryptoServiceProvider Info
[3] Custom WCF authentication
[4] Custom Message Encoder: Custom Text Encoder
[5] Cryptography Helper
[6] How to Get the AES Encryption Key from a RSA+AES Encrypted XML
[7] WCF GZip Compression Bug
[8] How to: Encrypt XML Elements with Asymmetric Keys
[9] How to: Encrypt XML Elements with Symmetric Keys
[10] The Universal Code Breaker
[11] Wcf ClearUsernameBinding
[12] Microsoft samples
[13] Microsoft code - Encoder/Factory
[14] Resolving XmlDictionaryReaderQuotas Error for WCF Compression using GZipEncoder with Custom Binding
[15] Man in the middle attack

History

2011-03-08 Version 1.0.0

  • Initial post

2011-03-15 Version 1.0.1

  • Small text changes

2011-03-17 Version 1.1.0

  • Updated Security Considerations with info about the man in the middle attack
  • Updated client-server flow - 4.b. to add the random client's password to the server's ban list
  • Updated code to treat the modified client server flow

2011-03-23 Version 1.1.1

  • Small text changes

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alexandru Lungu
Architect Adrem Invest SRL
Romania Romania
Alexandru Lungu graduated in 2004, first in his class, Cybernetics, Statistics and Computer Science faculty from A.S.E. Bucharest, and two years later finished his master in project management.
 
His motto: “Challenge is Life!”
 
Now, he works as project manager, developer, consultant depending on the “Challenge”.
 
You can reach him at challenge-me.ws
 

 

 
Don't forget to vote or share your comments.

Comments and Discussions

 
GeneralRe: Compression Before Encryption PinmemberAlexandru Lungu25-Mar-11 11:44 
GeneralRe: Compression Before Encryption PinmemberCP-Steve26-Mar-11 2:53 
GeneralUser / Password PinmemberMember 272481422-Mar-11 9:45 
GeneralRe: User / Password PinmemberAlexandru Lungu22-Mar-11 18:51 
GeneralRe: User / Password PinmemberMember 272481422-Mar-11 21:16 
GeneralMy vote of 5 Pinmemberjim lahey17-Mar-11 0:48 
GeneralRe: My vote of 5 PinmemberAlexandru Lungu22-Mar-11 18:57 
GeneralUses PinmemberMihaiDanceu16-Mar-11 14:16 
GeneralRe: Uses PinmemberAlexandru Lungu16-Mar-11 18:09 
GeneralMy vote of 5 PinmemberMihaiDanceu16-Mar-11 14:10 
GeneralRe: My vote of 5 PinmemberAlexandru Lungu16-Mar-11 22:10 
GeneralGood explanation, but contains security flaws PinmemberGLLNS15-Mar-11 10:57 
GeneralRe: Good explanation... [modified] PinmemberAlexandru Lungu15-Mar-11 12:23 
GeneralRe: Good explanation... PinmemberTom Spink16-Mar-11 9:09 
GeneralRe: Good explanation... PinmemberAlexandru Lungu16-Mar-11 17:42 
GeneralMy vote of 5 PinmemberKentuckyEnglishman15-Mar-11 3:17 
GeneralRe: My vote of 5 PinmemberAlexandru Lungu15-Mar-11 12:57 
GeneralMy vote of 5 Pinmemberpietvredeveld14-Mar-11 21:42 
GeneralRe: My vote of 5 PinmemberAlexandru Lungu15-Mar-11 12:27 
GeneralMy Vote of 5 PinmemberRaviRanjankr8-Mar-11 17:29 
GeneralRe: My Vote of 5 PinmemberAlexandru Lungu8-Mar-11 19:48 
GeneralMy vote of 5 PinmemberPatrick Kalkman8-Mar-11 9:09 
GeneralRe: My vote of 5 PinmemberAlexandru Lungu8-Mar-11 19:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 23 Mar 2011
Article Copyright 2011 by Alexandru Lungu
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid