Saturday, February 9, 2008

Using WSE 3.0 X509 Search API to implement RSA Crypto

WSE 3.0 has two functions to retrieve X509 Certificate:

X509Certificate2Collection cert2s = X509Util.FindCertificateBySubjectName(SubjectName, StoreLocation, StoreName.ToString());

X509Certificate2Collection cert2s = X509Util.FindCertificateByKeyIdentifier(ThumbPrint , StoreLocation, StoreName.ToString());

This is much simpler than using COM API or its .Net Wrapper. Using these function, we can write a class encapsulate RSA Crypto with considering of Certification Basic Policy Validation ( such as revocation):


namespace JQD
{
public class X509RSAEncryptor
{
#region Encryption and Decryption
public static string[] EncryptUTF8ToBase64(string ClearTextUTF8, X509Certificate2 cert2ForEncryption, X509Certificate2 cert2ForSignning)
{
if (null == cert2ForEncryption) throw new ApplicationException("null X509 cert for Encryption");
// create Encryption RSA using Public Key only
RSAParameters rsaForEncryptionPublicParam = X509Util.GetKey(cert2ForEncryption).ExportParameters(false);
RSACryptoServiceProvider rsaForEncryption = new RSACryptoServiceProvider();
rsaForEncryption.ImportParameters(rsaForEncryptionPublicParam);
// generate encrypted Base64 string from clear Text
byte[] clearBytes = Encoding.UTF8.GetBytes(ClearTextUTF8);
byte[] cipherBytes = rsaForEncryption.Encrypt(clearBytes, true);
string cipherTextBase64 = Convert.ToBase64String(cipherBytes);
string signatureTextBase64 = "";
if (null != cert2ForSignning)
{
// create Signning RSA using Private Key
RSAParameters rsaForSignningPrivateParam = X509Util.GetKey(cert2ForSignning).ExportParameters(true);
RSACryptoServiceProvider rsaForSignning = new RSACryptoServiceProvider();
rsaForSignning.ImportParameters(rsaForSignningPrivateParam);
// compute Signature using SHA1
byte[] signature = rsaForSignning.SignData(cipherBytes, SHA1.Create());
signatureTextBase64 = Convert.ToBase64String(signature);
rsaForSignning.Clear();
}
rsaForEncryption.Clear();
// send both Data and its singature as Base64 string
return new string[2] { cipherTextBase64, signatureTextBase64 };
}
public static string DecryptBase64ToUTF8(string[] CipherBase64, X509Certificate2 cert2ForDecryption, X509Certificate2 cert2ForVerifySignning)
{
if (null == cert2ForDecryption) throw new ApplicationException("null X509 cert for decryption");
byte[] cipherBytes = Convert.FromBase64String(CipherBase64[0]);
if (null != cert2ForVerifySignning)
{
// create Verify Signning RSA using public Key
RSAParameters rsaForVerifySignningPrivateParam = X509Util.GetKey(cert2ForVerifySignning).ExportParameters(false);
RSACryptoServiceProvider rsaForVerifySignning = new RSACryptoServiceProvider();
rsaForVerifySignning.ImportParameters(rsaForVerifySignningPrivateParam);
// Verify signature
byte[] signature = Convert.FromBase64String(CipherBase64[1]);
try
{
if (!rsaForVerifySignning.VerifyData(cipherBytes, SHA1.Create(), signature)) throw new ApplicationException("Data have been tampered.");
}
finally
{
rsaForVerifySignning.Clear();
}
}
// create Decryption RSA using Private Key
RSAParameters rsaForDecryptionPrivateParam = X509Util.GetKey(cert2ForDecryption).ExportParameters(true);
RSACryptoServiceProvider rsaForDecryption = new RSACryptoServiceProvider();
rsaForDecryption.ImportParameters(rsaForDecryptionPrivateParam);
// Decrypt and convert to Clear Text
byte[] clearBytes = rsaForDecryption.Decrypt(cipherBytes, true);
rsaForDecryption.Clear();
return Encoding.UTF8.GetString(clearBytes);
}
#endregion
#region X509 finder
public static X509Certificate2 FindX509Certificate2(string SubjectName, StoreLocation StoreLocation, StoreName StoreName)
{
X509Certificate2Collection cert2s = X509Util.FindCertificateBySubjectName(SubjectName, StoreLocation, StoreName.ToString());
X509Certificate2Collection validCert2s = Validate(cert2s);
if (validCert2s.Count == 0) return null;
return validCert2s[0];
}
public static X509Certificate2 FindX509Certificate2(byte[] ThumbPrint,StoreLocation StoreLocation, StoreName StoreName)
{
X509Certificate2Collection cert2s = X509Util.FindCertificateByKeyIdentifier(ThumbPrint , StoreLocation, StoreName.ToString());
X509Certificate2Collection validCert2s=Validate(cert2s);
if (validCert2s.Count == 0) return null;
return validCert2s[0];
}
private static X509Certificate2Collection Validate(X509Certificate2Collection cert2s)
{
X509Certificate2Collection validCert2s = new X509Certificate2Collection();
foreach (X509Certificate2 cert2 in cert2s)
{
// applies the base policy to that chain, Note that on Win2k3, the basic policy
// check conformance to RFC3280, which include revocation for X509 Cert2.
bool IsConformedToBasicPolicy = cert2.Verify();
// Some other simple checking
bool IsArchived = cert2.Archived;
bool IsExpired = (DateTime.Now > cert2.NotAfter)
bool IsNotActive =( DateTime.Now < cert2.NotBefore);
if (IsConformedToBasicPolicy && !IsArchived && !IsExpired && !IsNotActive)
{
validCert2s.Add(cert2);
}
}
return validCert2s;
}
#endregion
}
}

The usage is also simple as illustrated by the following:


class Program
{
static void Main(string[] args)
{
string msg="This is a secret.";
Console.WriteLine(msg);
X509Certificate2 cert2Enc;
X509Certificate2 cert2Sig;
string subjectName1="CN=idmcertid, OU=Internet Infrastructure, O=\"Blah Blah, Inc.\", L=Boston, S=massachusetts, C=US";
byte[] thumbPrint1=new byte[] { 0xb7, 0x4a, ....., 0x9c, 0x0c };

cert2Enc = X509RSAEncryptor.FindX509Certificate2(subjectName1,StoreLocation.LocalMachine, StoreName.My);
cert2Sig = X509RSAEncryptor.FindX509Certificate2("CN=XYZ Company", StoreLocation.CurrentUser, StoreName.My);
cert2Sig = X509RSAEncryptor.FindX509Certificate2(subjectName1, StoreLocation.CurrentUser, StoreName.My);
string[] cipherBase64 = X509RSAEncryptor.EncryptUTF8ToBase64(msg, cert2Enc, cert2Sig);
Console.WriteLine(cipherBase64[0]);
Console.WriteLine();
Console.WriteLine(cipherBase64[1]);
Console.WriteLine();
X509Certificate2 cert2Dec=X509RSAEncryptor.FindX509Certificate2(subjectName1,StoreLocation.LocalMachine, StoreName.My);
Console.WriteLine(X509RSAEncryptor.DecryptBase64ToUTF8(cipherBase64, cert2Dec, cert2Sig));
Console.ReadLine();
}

Note that when install Private Key, Must check " Mark this key Exportable" to avoid " Key in invalid state" exception. Also remember RSA encryption requires Receiving party to hold private key for decryption and hold public key for verify signning. Sending party hold just the opposite. So Receiveing party only need sending party's public key, and so on. This means it is better to have two computers with two set of good X509 Cert to run the code. If the cert is really invalid as seen in MMC IDE, IsConformedToBasicPolicy will be false.
Finally, There are no need to install WSE 3.0. You only need to have a copy of Microsoft.Web.Services3.dll and add its reference to your project.

Wednesday, February 6, 2008

Using Ajax Behavior to capture Before Unload event

In my previous post
http://jqdjha.blogspot.com/2008/02/handling-onbeforeunload-event-double.html
I discussed how to use setTimeout to avoid double firing onbeforeunload event in IE6 and IE7.
If the web page support ASP.Net Ajax Library, then we can use Ajax behavior to inject our code from that previous post to window, body, frameset tag, all of which support beforeunload event in IE. Note that we can control whether to attach "NavAway Behavior" or not:

<script>
      $create(JQD.NavAwayWarning,null,null,null,window);
</script>


var globalIsThe2ndNavAway=false;
Type.registerNamespace("JQD");


JQD.NavAwayWarning= function (element) {
      JQD.NavAwayWarning.initializeBase(this,[element]);
      this._navAwayHandler = Function.createDelegate(this,this._onNavAway);
}


JQD.NavAwayWarning.prototype = {

_onNavAway : function (e) {

      if (globalIsThe2ndNavAway) return;
      window.event.returnValue="You are losing changes."
      globalIsThe2ndNavAway=true;
      setTimeout("globalIsThe2ndNavAway=false;",0) ;

},

initialize: function () {
      JQD.NavAwayWarning.callBaseMethod(this,'initialize');
      $addHandler(this.get_element(),'beforeunload',this._navAwayHandler);
},

dispose: function() {
      JQD.NavAwayWarning.callBaseMethod(this,'dispose');
      $removeHandler(this.get_element(),'beforeunload',this._navAwayHandler);
}

}
JQD.NavAwayWarning.registerClass('JQD.NavAwayWarning', Sys.UI.Behavior);

Monday, February 4, 2008

Handling OnBeforeUnload Event Double Firing

IE 6.0 and 7.0 will fire onbeforeunload event twice if there is a link button on the ASP.Net page ( or in general an anchor with href set to a postback javascript). Here is the simple markup that "double firing" when postback happens:

<body onbeforeunload="alert();">
<form id="form1" runat="server">
<asp:LinkButton ID="LinkButton1" runat="server">LinkButton
</form>
</body>

The following javascript will make sure only the 1st Navigate-away event will invoke a message


var IsThe2ndNavAway=false;
function OBUL()
{
if (!IsThe2ndNavAway)
{
event.returnValue="Are you sure you want to lose your changes."
IsThe2ndNavAway=true;
setTimeout("IsThe2ndNavAway=false;",0)
}
}
Note that the 1st Nav-Away will execute the code to show message, etc. At the same time, 2nd will be blocked at function entry point since it is on the same thread as 1st Nav-Away.
As user dismiss the Message Box, the 2nd Nav-Away can no longer get into if-code block.
At the same time setTimeout will execute "IsThe2ndNavAway=false;" on a different thread so that the next round of "1st Nav-Away, 2nd Nav-Away" can contnue.

Saturday, January 26, 2008

How to use Footnote to annotate a Web Page

JHA Fundperformance uses Genearic List to render foonotes for product title and each fund. In general render footnotes requires insert DataBinding Expression at the right location and keep counter updated.
Here are the code for simplest Footnoting a Web Page:

<%@ Page Language="C#" ... Inherits="TestFootNotes._Default" %>

Test Footnoting a web page<br />
Web Pages are structure less<%# RenderFootNoteNumber("struct") %> writting of information. Therefore, annotate certain part of it requires putting a structure at certain location<%# RenderFootNoteNumber("location") %>.
<hr />
Footnotes: <br />
<asp:Repeater ID="Repeater1" runat="server" DataSource="<%# _FootnoteList %>">
<ItemTemplate>
[<%# Eval("key") %>]. <%# Eval("value") %><br />
</ItemTemplate>
</asp:Repeater>



public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
DataBind();
}
public Dictionary _FootnoteList = new Dictionary();
public string RenderFootNoteNumber(string AnnotationID)
{
int i=_FootnoteList.Count;
_FootnoteList.Add(i + 1, "Footnote Content for Annotation of " + AnnotationID );
return "<sup>["+_FootnoteList.Count.ToString()+"]</sup>";
}
}


The key is to have _FootNoteList as a holder of footnote counter and content as we going through the Web Page.

Friday, January 25, 2008

MVP Design Pattern vs. MVC design Pattern

JHA FundPerformance uses MVP ( Model View Presenter) design Pattern to render Fund Performance Data. In fact, ASP.Net uses Code-Behind to convert user action/events to Request to data. But JHA FundPerformance add one more layer MTData as DataObject. Specifically, Object Data Source explicitly separate Select/Insert/Update actions from other User Events handler in ASP.Net code-behind and that is the reason we called it "DataObject Presenter".

As a general rule, MVP requires user requests hit View first and then decide which Presenter method to call. On the contrary, MVC (Model View Controller) as implemented in ASP.Net MVC framework CTP routes user requests to Controller first and then decide which View to render.

Thursday, January 24, 2008

Two way databinding in ASP.net

It is very easy to one-way databind from code behind to Markup:

<%@ Page CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<%# Name %>

namespace WebApplication1
{
public partial class _Default : System.Web.UI.Page
{
public string Name
{
get { return "JHA"; }
}

The other way from markup back to code-behind is a little difficult to write but Microsoft has created "Object Data Source" to make picking up data much easier:


<Form runat="server">
<asp:textbox runat="server" id="tb1">
<asp:objectdatasource runat="server" id="ods1" runat="server"
TypeName="WebApplication1._Default" SelectMethod="Test">
<SelectParameters>
<asp:ControlParameter ControlID="tb1" Name="Email" />
</SelectParameters>
</asp:objectdatasource>
<asp:Button runat="server" />
</Form>

In essence, Databinding become two-way using ObjectDataSource Select, update, Insert methods.


protected override void OnPreRender(EventArgs e)
{
DataBind();
ods1.Select();
}
public void Test(string Email) // email parameter will be filled by text from tb1
{
}

How to build NUnitASP container using HTML source

JHA site uses nested Master Page to host content, eg. PrivateBase is inside Site.Master

<%@ Master MasterPageFile="Site.Master" Inherits="ManulifeUSA.Common.Web.UI.UserControls.PrivateBase" %>

<asp:Content ContentPlaceHolderID="mainContentPlaceHolder">

<asp:PlaceHolder ID="rightPlaceHolderContainer" runat="Server">

Note that "rightPlaceHolderContainer" is nested inside "mainContentPlaceHolder" as well.

This structure will generate the following HTML sourcece in AddressChangePage.aspx as follows:

<input type="submit" id="ctl00_ctl00_mainContentPlaceHolder_rightPlaceHolder_btnUpdateAddress" />

Note that the id is the clue for constructing containers for NUnitASP testing, namely, ctl00 referes to SiteMaster, the 2nd ctl00 refers to PrivateBase, etc.
Since I have abstracted "ctl00_ctl00_mainContentPlaceHolder" into the following Test Container:

Public Class PrivateBaseTester
Inherits UserControlTester
Sub New(ByVal SiteID As String, ByVal PrivateBaseID As String, ByVal rightPlaceHolderID As String)
MyBase.New(rightPlaceHolderID, New UserControlTester("mainContentPlaceHolder",

New UserControlTester(PrivateBaseID, New UserControlTester(SiteID))))
End Sub
End Class


You would need to construct containers for btnUpdateAddress in the following manner:

Dim master As New PrivateBaseTester("ctl00", "ctl00", "rightPlaceHolder")

Dim btnUpdateAddress As New ButtonTester("btnUpdateAddress", master)

I hope this will help developers understand the approach to create container chains.