Click here to Skip to main content
15,891,725 members
Articles / Web Development / ASP.NET

Transparent Cookie Encryption Via HTTP Module

Rate me:
Please Sign up or sign in to vote.
3.54/5 (7 votes)
5 Jul 2009CPOL4 min read 53.2K   384   23  
A C# HTTP module that encrypts and decrypts cookies transparently to an application
using System;
using System.Collections;
using System.Configuration;
using System.Diagnostics;
using System.Web;
using System.Web.Security;

namespace Utilities
{
	/// <summary>
	/// Implements cookie encryption according to web.config settings
	/// </summary>
	public class CookieEncryptionModule : IHttpModule
	{		
		#region Class level variables
		//	Cookies in this ArrayList will never be processed	
		private readonly ArrayList NeverProcessTheseCookies = new ArrayList(new string[]{"ASP.NET_SessionId"});
		
		//	How to handle cookies that are unencrypted on the client but should now be encrypted
		UnencryptedCookiePolicy ApplicationUnencryptedCookiePolicy = new UnencryptedCookiePolicy(UnencryptedCookiePolicy.Clear);

		//	CookieList determines which cookies will be handled or ignored
		ArrayList CookieList = new ArrayList();

		//	HandleCookieList - 
		//		if true:
		//			the cookies in the list will be handled and all other cookies will be ignored
		//		if false:
		//			the the cookies in the list will be ignored and all other cookies will be handled
		bool HandleCookieList = false;

		private enum Action
		{
			Decrypt,
			Encrypt
		}
			
		#region ValidValues - web.config value messages
		private struct ValidValues
		{
			internal const string CookieEncryptionModule_HandleOrIgnoreCookieList = "Handle / Ignore";
			internal const string CookieEncryptionModule_HandleOrIgnoreCookieList_Test = "|HANDLE|IGNORE|";
			internal const string CookieEncryptionModule_UnencryptedCookiePolicy = "Clear / Session / Never / dd,hh,mm,ss";
		}
		#endregion	

		#region UnencryptedCookiePolicy structure definition
		internal struct UnencryptedCookiePolicy
		{
			internal static readonly DateTime NeverExpires = new DateTime(2099,1,1);
			internal const string Ignore = "Ignore";
			internal const string Clear = "Clear";
			internal const string Session = "Session";
			internal const string Never = "Never";
			internal const string SetExpiration = "SetExpiration";

			internal string Option;
			internal bool ValidConstruction;
			private TimeSpan AddToAddYears;
			private int AddYears;

			internal Exception Lastexception;

			public UnencryptedCookiePolicy(string option)
			{
				if(IsValid(option, out AddYears, out AddToAddYears, out Lastexception))
				{
					switch(option)
					{
						case Ignore:
						case Clear:
						case Session:
						case Never:
							Option = option;
							break;
						default:	//	This is a timespan string
							Option = SetExpiration;
							break;
					}
					ValidConstruction = true;
				}
				else
				{
					Option = Clear;
					ValidConstruction = false;
				}
			}

			public void ProcessCookie(HttpCookie cookieToHandle)
			{
				switch(Option)
				{
					case Ignore:
						break;
					case Session:
						HttpContext.Current.Response.Cookies.Add(cookieToHandle);
						break;
					case Never:
						cookieToHandle.Expires = NeverExpires;
						HttpContext.Current.Response.Cookies.Add(cookieToHandle);
						break;
					case SetExpiration:						
						cookieToHandle.Expires = DateTime.Now;
						cookieToHandle.Expires = cookieToHandle.Expires.AddYears(AddYears);
						cookieToHandle.Expires = cookieToHandle.Expires.Add(AddToAddYears);
						HttpContext.Current.Response.Cookies.Add(cookieToHandle);
						break;
					case Clear:
					default:
						HttpContext.Current.Request.Cookies.Remove(cookieToHandle.Name);
						break;
				}
			}

			public static bool IsValid(string isValue, out int addYears, out TimeSpan expiresIn, out Exception lastexception)
			{
				bool ReturnValue = false;

				addYears = 0;
				expiresIn = TimeSpan.MinValue;
				lastexception = null;

				switch(isValue)
				{
					case Ignore:
					case Clear:
					case Session:
					case Never:
						ReturnValue = true;
						break;
					default:
						ReturnValue = IsTimeSpanWithYear(isValue, ref addYears, ref expiresIn, ref lastexception);
						break;
				}

				return ReturnValue;
			}

			private static bool IsTimeSpanWithYear(string isValue, ref int addYears, ref TimeSpan timespan, ref Exception exception)
			{
				bool ReturnValue = false;

				string[] TimeSpanWithYear = isValue.Split(new char[]{','},5);
				if(TimeSpanWithYear.Length == 5)
				{
					try
					{
						addYears = int.Parse(TimeSpanWithYear[0]);
						timespan = new TimeSpan(
							int.Parse(TimeSpanWithYear[1]),
							int.Parse(TimeSpanWithYear[2]),
							int.Parse(TimeSpanWithYear[3]),
							int.Parse(TimeSpanWithYear[4]));

						ReturnValue = true;
					}
					catch(Exception ex)
					{
						exception = ex;
					}
				}

				return ReturnValue;
			}
		}
		#endregion		
		#endregion

		#region	Constructor
		public CookieEncryptionModule()
		{
			bool AllConfigItemsValid = false;	//	For security's sake - assume the worst
			ArrayList TempCookieList;

			//	Want to default to encrypting all if anything unexpected happens
			SetMostSecure();

			string CookieEncryptionModule_Separator = ConfigurationSettings.AppSettings["CookieEncryptionModule_Separator"];
			string CookieEncryptionModule_CookieList = ConfigurationSettings.AppSettings["CookieEncryptionModule_CookieList"];
			string CookieEncryptionModule_HandleOrIgnoreCookieList = ConfigurationSettings.AppSettings["CookieEncryptionModule_HandleOrIgnoreCookieList"];
			string CookieEncryptionModule_UnencryptedCookiePolicy = ConfigurationSettings.AppSettings["CookieEncryptionModule_UnencryptedCookiePolicy"];

			if(CookieEncryptionModule_Separator == null && CookieEncryptionModule_CookieList == null && CookieEncryptionModule_HandleOrIgnoreCookieList == null)
			{	//	Nothing in web.config so, since this module has been linked, encrypt all cookies
				SetMostSecure();
			}
			else
			{
				while(true)	//	Dummy loop to allow break
				{
					if (CookieEncryptionModule_Separator == null)
					{
						HandleError(new ArgumentNullException("CookieEncryptionModule_Separator", this.GetType().ToString() + " - required web.config value (single character) is missing"));
						break;
					}
			
					CookieEncryptionModule_Separator = CookieEncryptionModule_Separator.Trim();
					if (CookieEncryptionModule_Separator.Length !=1)
					{
						HandleError(new ArgumentOutOfRangeException("CookieEncryptionModule_Separator", this.GetType().ToString() + " - value must be a single character"));
						break;
					}
					
					char Separator = CookieEncryptionModule_Separator[0];

					if(CookieEncryptionModule_HandleOrIgnoreCookieList == null)
					{
						HandleError(new ArgumentNullException("CookieEncryptionModule_HandleOrIgnoreCookieList", this.GetType().ToString() + " - required web.config value is missing"));
						break;
					}

					CookieEncryptionModule_HandleOrIgnoreCookieList = CookieEncryptionModule_HandleOrIgnoreCookieList.Trim().ToUpper();
					if(ValidValues.CookieEncryptionModule_HandleOrIgnoreCookieList_Test.IndexOf("|" + CookieEncryptionModule_HandleOrIgnoreCookieList + "|") == -1)
					{
						HandleError(new ArgumentOutOfRangeException("CookieEncryptionModule_HandleOrIgnoreCookieList", this.GetType().ToString() + " - value must be " + ValidValues.CookieEncryptionModule_HandleOrIgnoreCookieList));
						break;
					}				

					if (CookieEncryptionModule_CookieList == null)
					{
						HandleError(new ArgumentNullException("CookieEncryptionModule_CookieList", this.GetType().ToString() + " - required web.config value is missing"));
						break;
					}
			
					CookieEncryptionModule_CookieList = CookieEncryptionModule_CookieList.Trim();
					if (CookieEncryptionModule_CookieList == string.Empty) 
					{
						HandleError(new ArgumentOutOfRangeException("CookieEncryptionModule_CookieList", this.GetType().ToString() + " - value cannot be empty"));
						break;
					}

					try
					{
						TempCookieList = new ArrayList(CookieEncryptionModule_CookieList.Split(Separator));
					}
					catch(Exception ex)
					{
						HandleError(new InvalidOperationException("web.config value CookieEncryptionModule_CookieList could not be separated", ex));
						break;
					}

					UnencryptedCookiePolicy  TempApplicationUnencryptedCookiePolicy = new UnencryptedCookiePolicy(CookieEncryptionModule_UnencryptedCookiePolicy);
					if(!ApplicationUnencryptedCookiePolicy.ValidConstruction)
					{
						string Message = this.GetType().ToString() + " - value must be " + ValidValues.CookieEncryptionModule_UnencryptedCookiePolicy + "\n";
						if(ApplicationUnencryptedCookiePolicy.Lastexception != null) Message += ApplicationUnencryptedCookiePolicy.Lastexception.ToString();
						HandleError(new ArgumentOutOfRangeException("CookieEncryptionModule_UnencryptedCookiePolicy", Message));
						break;
					}

					//	Need to be sure that we only set the main values if everything is OK
					AllConfigItemsValid = true;					
					HandleCookieList = (CookieEncryptionModule_HandleOrIgnoreCookieList == "HANDLE");
					ApplicationUnencryptedCookiePolicy = TempApplicationUnencryptedCookiePolicy;
					CookieList = TempCookieList;
					break;
				}

				//	If there was an invalid config item then default to encrypting all cookies
				if(!AllConfigItemsValid) SetMostSecure();
			}
		}

		#endregion

		#region IHttpModule Members

		public void Init(HttpApplication httpApplication)
		{
			httpApplication.BeginRequest += new System.EventHandler(httpApplication_BeginRequest);
			httpApplication.EndRequest +=new EventHandler(httpApplication_EndRequest);
		}

		public void Dispose()
		{
		}
		#endregion

		#region Registered event handlers
		public void httpApplication_BeginRequest(object sender, EventArgs e)
		{
			HttpApplication httpApplication = (HttpApplication)sender;

			//	Decrypt cookies
			HttpCookieCollection Cookies = httpApplication.Request.Cookies;
			string[] CookieKeys = Cookies.AllKeys;
			foreach(string CurrentCookieKey in CookieKeys)
			{				
				HandleCookie(Cookies[CurrentCookieKey], Action.Decrypt);
			}
		}

		private void httpApplication_EndRequest(object sender, EventArgs e)
		{
			HttpApplication httpApplication = (HttpApplication)sender;

			//	Encrypt new / modifiedcookies
			HttpCookieCollection Cookies = httpApplication.Response.Cookies;
			string[] CookieKeys = Cookies.AllKeys;
			foreach(string CurrentCookieKey in CookieKeys)
			{
				HandleCookie(Cookies[CurrentCookieKey], Action.Encrypt);
			}
		}
		#endregion

		#region Private methods
		private void HandleCookie(HttpCookie cookieToHandle, Action action)
		{			
			//	Don't process cookie if it's in the 'never process' list
			if(NeverProcessTheseCookies.Contains(cookieToHandle.Name) ) return;

			bool CookieInList = CookieList.Contains(cookieToHandle.Name);
			
			//	If the cookie is in the list and we are handling the cookie list then handle the cookie 
			//		OR
			//	If the cookie isn't in the list but we are ignoring the cookie list then handle the cookie
			//	Translates to: Not (A xor B)
			if(!(CookieInList ^ HandleCookieList) )
			{
				try
				{
					FormsAuthenticationTicket CookieTicket;
			
					switch(action)
					{
						case Action.Decrypt:
							try
							{
								CookieTicket = FormsAuthentication.Decrypt(cookieToHandle.Value);
								cookieToHandle.Value = CookieTicket.UserData;
							}
							catch
							{							
								//	This cookie is not encrypted but it should be
								//	Process the cookie according to ApplicationUnencryptedCookiePolicy
								ApplicationUnencryptedCookiePolicy.ProcessCookie(cookieToHandle);
							}
							break;
						case Action.Encrypt:
							DateTime DummyDateTime = DateTime.Now;
							CookieTicket = new FormsAuthenticationTicket(1, "DummyName", DummyDateTime, DummyDateTime, false, cookieToHandle.Value);
							cookieToHandle.Value = FormsAuthentication.Encrypt(CookieTicket);
							break;
					}
				}
				catch(Exception ex)	//	There was a problem so leave the cookie alone
				{
					HandleError(ex);
				}
			}
		}

		private void HandleError(Exception ex)
		{
			try
			{					
				EventLog.WriteEntry("Application Error",  "\nCookie Encryption Problem\n" + ex.ToString(), EventLogEntryType.Error);
			}
			catch
			{
				throw;
			}
		}

		private void SetMostSecure()
		{
			CookieList = new ArrayList();
			HandleCookieList = false;
			ApplicationUnencryptedCookiePolicy.Option = UnencryptedCookiePolicy.Clear;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions