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
}
}