using System;
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Diagnostics;
public class PDFXSSFilter : System.Web.IHttpHandler
{
private string _EncryptionKey;
public PDFXSSFilter()
{
//
// TODO: Add constructor logic here
//
}
#region IHttpHandler Members
public void ProcessRequest(HttpContext context)
{
_EncryptionKey = this.GetEncryptionKey();
HttpRequest request = context.Request;
string pdfFileName = request.Url.Segments[request.Url.Segments.Length - 1];
try
{
string tokenvalue = null;
try
{
tokenvalue = request.QueryString["p"];
}
catch (System.NullReferenceException nex)
{
// This isn't good style - just trying to make this work first.
}
// Check if the token value is defined in the URL
if (tokenvalue == null)
{
// If not, calculate the secure value
// string x = context.Server.UrlEncode(EncryptData(request.ServerVariables["REMOTE_ADDR"]));
string x1 = EncryptData(request.ServerVariables["REMOTE_ADDR"]);
this.Trace(context, "pvalue", x1);
this.Trace(context, "remoteip", request.ServerVariables["REMOTE_ADDR"]);
string x = context.Server.UrlEncode(x1);
// Now build the redrect URL
string redirectURL = request.Url.AbsolutePath + "?p=" + x + "#a";
// Now redirect them to the calculated URL
context.Response.Redirect(redirectURL, false);
}
else
{
// Remove the javascript behind first
tokenvalue = tokenvalue.Split("#".ToCharArray(), 2)[0];
// Decode the token next
tokenvalue = context.Server.UrlDecode(tokenvalue).Replace(' ', '+');
this.Trace(context, "tokenvalue", tokenvalue);
this.Trace(context, "remoteip", request.ServerVariables["REMOTE_ADDR"]);
// Check if token data is valid
if (ValidateData(tokenvalue, request.ServerVariables["REMOTE_ADDR"], context))
{
// If so, transmit as PDF
context.Response.ContentType = "application/pdf";
}
else
{
//Otherwise, transmit as an octet-stream (ie, force download)
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Disposition", "attachment; filename=" + pdfFileName);
this.Trace(context, "condition", "Returned false");
}
//Transmit the file regardless
context.Response.TransmitFile(context.Server.MapPath(request.FilePath));
}
}
catch (System.Exception ex)
{
// Not the cleanest method, but if there are any exceptions, just pass the file as a forced download.
context.Response.ContentType = "application/octet-stream";
context.Response.AddHeader("Content-Disposition", "attachment; filename=" + pdfFileName);
context.Response.TransmitFile(context.Server.MapPath(request.FilePath));
this.Trace(context, "final_catch", ex.Message);
}
}
// Method to encrypt the data
private string EncryptData(string data)
{
RijndaelManaged rc = new RijndaelManaged();
string newdata = System.DateTime.Now.ToString() + "," + data;
// Build the plain text out of the data and the current DateTime
byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(newdata);
byte[] Salt = Encoding.ASCII.GetBytes(_EncryptionKey.Length.ToString());
// Salt and encrypt the data - calculate a Base64 string
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(_EncryptionKey, Salt);
ICryptoTransform Encryptor = rc.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, Encryptor, CryptoStreamMode.Write);
cs.Write(PlainText, 0, PlainText.Length);
cs.FlushFinalBlock();
byte[] CipherBytes = ms.ToArray();
ms.Close();
cs.Close();
string EncryptedData = Convert.ToBase64String(CipherBytes);
// Return with a SHA512 hash appended
return EncryptedData.ToString() + ":" + GetSHA512Hash(newdata);
}
// Validate the token data
private bool ValidateData(string data, string remoteip, HttpContext context)
{
// Split into the ciphertext and the SHA512 hash
string[] components = data.Split(new char[] { ':' });
string CipherText = components[0];
string SHA512Hash;
try
{
SHA512Hash = components[1];
}
catch (System.ArgumentOutOfRangeException aex)
{
this.Trace(context, "exception", "ArgumentOutOfRange");
// If no hash is found, throw an invalid token exception
throw new InvalidPDFTokenException();
}
// Decrypt the data
RijndaelManaged RijndaelCipher = new RijndaelManaged();
byte[] EncryptedData = Convert.FromBase64String(CipherText);
byte[] Salt = Encoding.ASCII.GetBytes(_EncryptionKey.Length.ToString());
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(_EncryptionKey, Salt);
ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream(EncryptedData);
CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
byte[] PlainText = new byte[EncryptedData.Length];
int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
memoryStream.Close();
cryptoStream.Close();
string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);
// Check if hash of the Decrypted data matches the stored hash
if (SHA512Hash != GetSHA512Hash(DecryptedData))
{
this.Trace(context, "exception", "hash does not match");
throw new InvalidPDFTokenException("Token Invalid");
}
// Split into IP Address and date time
string[] values = DecryptedData.Split(new char[] { ',' });
if (values.Length == 2)
{
// Verify that the date time is within x seconds and the IPs match
int tokentimeout;
try
{
tokentimeout = System.Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["TokenTimeout"]);
}
catch (System.Exception ex)
{
tokentimeout = 10;
}
if (System.DateTime.Now < System.Convert.ToDateTime(values[0]).AddSeconds(tokentimeout) && values[1] == remoteip)
{
return true;
}
}
return false;
}
// Generate the SHA512 Hash
private string GetSHA512Hash(string data)
{
SHA512Managed _sha512 = new SHA512Managed();
byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(data);
byte[] sha512signature = _sha512.ComputeHash(PlainText);
string sha512Hash = Convert.ToBase64String(sha512signature);
return sha512Hash;
}
// Read the encryption key from the config file (typically an AppSettings entry in web.config)
private string GetEncryptionKey()
{
string key = System.Configuration.ConfigurationSettings.AppSettings
["TokenEncryptionKey"];
if (key == null || key == String.Empty)
throw new InvalidPDFTokenException
("TokenEncryptionKey missing");
return key;
}
// Required by the IHttpHandler interface
public bool IsReusable
{
get
{
return true;
}
}
[Conditional("DEBUG")]
public void Trace(HttpContext context, string name, string msg)
{
context.Response.AddHeader("DEBUG_"+name, msg);
}
#endregion
}
// Custom exception type
[Serializable]
public class InvalidPDFTokenException : Exception
{
public InvalidPDFTokenException()
:
base("PDF Token data is invalid") { }
public InvalidPDFTokenException(string message)
:
base(message) { }
public InvalidPDFTokenException(string message,
Exception inner)
: base(message, inner) { }
protected InvalidPDFTokenException(SerializationInfo info,
StreamingContext context)
: base(info, context) { }
}