using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace GSS.Web.Security
{
public class FormsAuthentication
{
private const int Max_Cookie_Size = 4096;
private static FormsAuthConfig _config = new FormsAuthConfig();
/// <summary>
/// Formats and Encrypts a FormsAuthenticationTicket
/// </summary>
/// <param name="ticket">The ticket to encrypt</param>
/// <returns>Hex string representation of the encrypted ticket bytes</returns>
public static string Encrypt(FormsAuthenticationTicket ticket)
{
try
{
byte[] createTimeBytes = Utils.DateToByteArray(ticket.IssueDate);
byte[] expireTimeBytes = Utils.DateToByteArray(ticket.Expires);
byte[] userBytes = Encoding.Unicode.GetBytes(ticket.Username);
byte[] userDataBytes = Encoding.Unicode.GetBytes(ticket.UserData);
byte[] appPathBytes = Encoding.Unicode.GetBytes(ticket.AppPath);
byte[] randomBytes = Utils.GetRandomBytes(8);
byte ver = (byte)ticket.Version;
byte persist = 0;
if (ticket.IsPersistent)
persist = 1;
byte[] ticketBytes = MakeTicketBlob(randomBytes, ver, userBytes, createTimeBytes, persist, expireTimeBytes, userDataBytes, appPathBytes);
ticketBytes = SignTicket(ticketBytes);
ticketBytes = EncryptTicket(ticketBytes);
String encryptedTicket = Utils.ByteArrayToHexString(ticketBytes);
return encryptedTicket;
}
catch (Exception ex)
{
LogError(ex.ToString());
return string.Empty;
}
}
/// <summary>
/// Creates a binary blob of the provided byte arrays
/// </summary>
/// <param name="random">8 byte array of random data</param>
/// <param name="version">the version of the ticket</param>
/// <param name="username">byte array containing the unicode formatted username</param>
/// <param name="issueDate">long byte array containing the FileTime the ticket was issued</param>
/// <param name="persist">byte representing the boolean value indicating whether or not the ticket should be persisted</param>
/// <param name="expires">long byte array containing the FileTime at which the ticket will expire</param>
/// <param name="userdata">byte array containing the unicode formatted user data</param>
/// <param name="appPath">byte array containing the unicode formatted application path ie. "/appPath"</param>
/// <returns>a binary blob (byte array) formatted to exactly match ASP.Net FormsAuthenticationTicket binary blob</returns>
private static byte[] MakeTicketBlob(byte[] random, byte version, byte[] username,
byte[] issueDate, byte persist, byte[] expires, byte[] userdata, byte[] appPath)
{
if (random.Length != 8)
throw new ArgumentException("random");
byte[] buffer = null;
int bufferLength = 7;
bufferLength += random.Length;
bufferLength += username.Length;
bufferLength += issueDate.Length;
bufferLength += expires.Length;
if (userdata != null)
bufferLength += userdata.Length;
bufferLength += appPath.Length;
buffer = new byte[bufferLength];
int pos = 0;
//int len = 0;
Buffer.BlockCopy(random, 0, buffer, pos, random.Length);
pos += random.Length;
buffer[pos] = version;
pos++;
Buffer.BlockCopy(username, 0, buffer, pos, username.Length);
pos += username.Length;
buffer[pos] = 0;
pos++;
buffer[pos] = 0;
pos++;
Buffer.BlockCopy(issueDate, 0, buffer, pos, issueDate.Length);
pos += issueDate.Length;
buffer[pos] = persist;
pos++;
Buffer.BlockCopy(expires, 0, buffer, pos, expires.Length);
pos += expires.Length;
if (userdata != null)
{
Buffer.BlockCopy(userdata, 0, buffer, pos, userdata.Length);
pos += userdata.Length;
}
buffer[pos] = 0;
pos++;
buffer[pos] = 0;
pos++;
Buffer.BlockCopy(appPath, 0, buffer, pos, appPath.Length);
pos += appPath.Length;
buffer[pos] = 0;
return buffer;
}
/// <summary>
/// Create and HMAC signature (hash) of the provide byte array
/// </summary>
/// <param name="ticketBytes">bytes to sign</param>
/// <returns>The signature of the "ticketBytes"</returns>
private static byte[] SignTicket(byte[] ticketBytes)
{
byte[] signedTicket = null;
byte[] signature = _config.ComputeHash(ticketBytes);
signedTicket = new byte[ticketBytes.Length + signature.Length];
Buffer.BlockCopy(ticketBytes, 0, signedTicket, 0, ticketBytes.Length);
Buffer.BlockCopy(signature, 0, signedTicket, ticketBytes.Length, signature.Length);
return signedTicket;
}
/// <summary>
/// Encrypts the formatted ticket blob
/// </summary>
/// <param name="ticketBytes">formatted ticket blob</param>
/// <returns>Encrypted ticket blob</returns>
private static byte[] EncryptTicket(byte[] ticketBytes)
{
try
{
using (MemoryStream ms = new MemoryStream())
{
ICryptoTransform transform = _config.GetTransform(false);
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
{
cs.Write(ticketBytes, 0, ticketBytes.Length);
cs.FlushFinalBlock();
cs.Close();
}
byte[] encryptedBytes = ms.ToArray();
ms.Close();
return encryptedBytes;
}
}
catch { throw; }
}
/// <summary>
/// Creates a FormsAuthentication ticket from the encrypted Data
/// </summary>
/// <param name="encryptedTicket">Encrypted ticket string</param>
/// <returns>FormsAuthentication ticket or "null" if the ticket is
/// formatted incorrectly, crypto graphic errors occur, or if the signature is invalid</returns>
public static FormsAuthenticationTicket Decrypt(string encryptedTicket)
{
if (string.IsNullOrEmpty(encryptedTicket) || encryptedTicket.Length > Max_Cookie_Size)
throw new ArgumentException("encryptedTicket");
try
{
FormsAuthenticationTicket ticket = null;
byte[] decryptedData = DecryptTicket(encryptedTicket);
if (!IsSignatureVerified(decryptedData))
return ticket;
byte[] ticketBytes = new byte[decryptedData.Length - 20];
Buffer.BlockCopy(decryptedData, 0, ticketBytes, 0, ticketBytes.Length);
ticket = ParseTicket(ticketBytes);
return ticket;
}
catch (Exception ex)
{
LogError(ex.ToString());
return null;
}
}
/// <summary>
/// Parses the decrypted ticket blob (byte array) and constructs a FormsAuthenticationTicket
/// </summary>
/// <param name="ticketBytes">Decrypted ticket blob (byte array)</param>
/// <returns>FormsAuthencticationTicket or throws and error if the ticket is formatted incorrectly</returns>
private static FormsAuthenticationTicket ParseTicket(byte[] ticketBytes)
{
FormsAuthenticationTicket ticket = null;
int version;
int pos = 8;
int usernameStart = -1;
int usernameEnd = -1;
int isPersist = -1;
int userDataStart = -1;
int userDataEnd = -1;
int appPathStart = -1;
int appPathEnd = -1;
bool isFirstPass = true;
byte currentByte;
byte nextByte;
byte[] createTimeBytes = new byte[8];
byte[] expireTimeBytes = new byte[8];
version = (int)ticketBytes[pos];
pos++;
while (pos < ticketBytes.Length)
{
currentByte = ticketBytes[pos];
if (isFirstPass)
usernameStart = pos;
isFirstPass = false;
pos++;
nextByte = ticketBytes[pos];
if (currentByte == 0 && nextByte == 0)
{
usernameEnd = pos - 1;
pos++;
break;
}
}
pos++;
for (int i = 0; i < 8; i++)
{
createTimeBytes[i] = ticketBytes[pos];
pos++;
}
isPersist = (int)ticketBytes[pos];
pos++;
for (int i = 0; i < 8; i++)
{
expireTimeBytes[i] = ticketBytes[pos];
pos++;
}
isFirstPass = true;
while (pos < ticketBytes.Length)
{
currentByte = ticketBytes[pos];
if (isFirstPass && currentByte != 0)
{
userDataStart = pos;
}
if (isFirstPass && currentByte == 0)
{
// pos++;
break;
}
isFirstPass = false;
pos++;
nextByte = ticketBytes[pos];
if (currentByte == 0 && nextByte == 0)
{
userDataEnd = pos - 1;
break;
}
}
pos++;
isFirstPass = true;
while (pos < ticketBytes.Length)
{
currentByte = ticketBytes[pos];
if (isFirstPass)
appPathStart = pos + 1;
isFirstPass = false;
pos++;
nextByte = ticketBytes[pos];
if (currentByte == 0 && nextByte == 0)
{
appPathEnd = pos - 1;
break;
}
}
try
{
int byteCount = (usernameEnd - usernameStart + 1);
byte[] buffer = new byte[byteCount];
Buffer.BlockCopy(ticketBytes, usernameStart, buffer, 0, byteCount);
String username = Encoding.Unicode.GetString(buffer);
String userData = "";
if (userDataStart != -1)
{
byteCount = userDataEnd - userDataStart + 1;
buffer = new byte[byteCount];
Buffer.BlockCopy(ticketBytes, userDataStart, buffer, 0, byteCount);
userData = Encoding.Unicode.GetString(buffer);
}
String appPath = "";
byteCount = appPathEnd - appPathStart + 1;
buffer = new byte[byteCount];
Buffer.BlockCopy(ticketBytes, appPathStart, buffer, 0, byteCount);
appPath = Encoding.Unicode.GetString(buffer);
DateTime createTime = Utils.ByteArrayToDate(createTimeBytes);
DateTime expiration = Utils.ByteArrayToDate(expireTimeBytes);
bool isPersistent;
if (isPersist == 1)
isPersistent = true;
else
isPersistent = false;
ticket = new FormsAuthenticationTicket(username, userData, appPath, createTime, expiration, version, isPersistent);
}
catch
{
throw;
}
return ticket;
}
/// <summary>
/// Checks to ensure the Hmac signature is valid
/// </summary>
/// <param name="decryptedData">Decrypted ticket blob (byte array)</param>
/// <returns>true is the signature is valid, false if now</returns>
private static bool IsSignatureVerified(byte[] decryptedData)
{
byte[] signatureBytes = _config.ComputeHash(decryptedData, 0, decryptedData.Length - 20);
for (int i = 0; i < 20; i++)
{
if (signatureBytes[i] != decryptedData[(decryptedData.Length - 20) + i])
return false;
}
return true;
}
/// <summary>
/// Decryptes the encrypted ticket
/// </summary>
/// <param name="encryptedTicket">Encrypted Ticket</param>
/// <returns>binary blob (byte array, or throws an execption</returns>
private static byte[] DecryptTicket(string encryptedTicket)
{
try
{
ICryptoTransform transform = _config.GetTransform(true);
byte[] encryptedBytes = Utils.HexStringToByteArray(encryptedTicket);
byte[] decryptedBytes = null;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Write))
{
cs.Write(encryptedBytes, 0, encryptedBytes.Length);
cs.FlushFinalBlock();
cs.Close();
decryptedBytes = ms.ToArray();
ms.Close();
}
}
return decryptedBytes;
}
catch
{
throw;
}
}
private static void LogError(string error)
{
string logPath = string.Empty;
if (_config == null || string.IsNullOrEmpty(_config.LogPath))
logPath = "FormsAuth.log";
else
logPath = _config.LogPath;
try
{
using (StreamWriter sw = new StreamWriter(logPath, true))
{
sw.Write(DateTime.Now.ToString());
sw.WriteLine(":");
sw.WriteLine(error);
sw.WriteLine();
sw.Close();
}
}
catch { }
}
}
}