Monday, June 30, 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(cert2 ForEncryption).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.

Saturday, June 21, 2008

Load CLR runtime into XIP addin unmanaged process

LPWSTR pszVer = L"v2.0.50727";
LPWSTR pszFlavor = L"svr"; // svr, wks
ICorRuntimeHost *pHost =NULL;
mscorlib::_AppDomain *pDefaultDomain = NULL;
IUnknown *pAppDomainPunk = NULL;
STDMETHODIMP CXIPVCAddin::AppStartUp( VARIANT_BOOL* pnComponentInitialized)
{

HRESULT hr =::CorBindToRuntimeEx(pszVer, pszFlavor,
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN ,
CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (void **)&pHost);
pHost->Start();

hr = pHost->GetDefaultDomain(&pAppDomainPunk);
hr = pAppDomainPunk->QueryInterface(__uuidof(mscorlib::_AppDomain),
(void**) &pDefaultDomain);


JQDXIPEventHandler::IXIPEventHandler *pObj=NULL;
mscorlib::_ObjectHandle *pObjHandle = NULL;

_bstr_t a("JQDXIPEventHandler");
_bstr_t t("JQD.XIPEventHandler");

hr=pDefaultDomain->CreateInstance(a,t,&pObjHandle);

VARIANT v;
VariantInit(&v);
hr = pObjHandle->Unwrap(&v);

hr = v.pdispVal->QueryInterface(__uuidof(JQDXIPEventHandler::XIPEventHandler),
(void**) &pObj);
hr=pObj->OnEvent();


MessageBox(NULL,"AppStartUp with CLR loaded, MFC/ATL","IXIPAddIn Sample",MB_OK);
*pnComponentInitialized=VARIANT_TRUE;
return S_OK;
}

STDMETHODIMP CXIPVCAddin::AppShutDown (VARIANT_BOOL* pnComponentDisconnected)
{
pHost->Stop();

pAppDomainPunk->Release();
pDefaultDomain->Release();
pHost->Release();
//MessageBox(NULL,"AppShutDownB with CLR Cleaned","IXIPAddIn Sample",MB_OK);
*pnComponentDisconnected=VARIANT_TRUE;
return S_OK;
}

STDMETHODIMP CXIPVCAddin::OnEvent (BSTR bstrEventName, IDispatch* pObject, VARIANT vArg)
{

MessageBox(NULL,"OnEvent","IXIPAddIn Sample",MB_OK);
return S_OK;
}

Thursday, June 19, 2008

XIP Addin COM object fail to load .Net code

This could be a general problem, i.e., when a win32 process is loading a COM object, we can get "Class not registered" error, if one of the following is true:
(1) The COM object uses CoCreateInstance to initalized C# comsible .Net class
(2) Teh COM object uses C++ Mixed Managed and unmanaged code feature.

Here are the code snippets:

// C# COM object
#import "C:\Working\JQDXIPCOMCS\Debug\JQDXIPCOMCS.tlb"
STDMETHODIMP CXIPVCAddin::AppStartUp( VARIANT_BOOL* pnComponentInitialized)
{
HRESULT hr = CoInitialize(NULL);
JQDXIPCOMCS::IXIPEventHandlerPtr pEH(__uuidof(JQDXIPCOMCS::XIPEventHandler));


// ATL C++ COM with /clr at project or file level
#import "C:\Working\JQDTestCOM\JQDTestCOM\Debug\JQDTestCOM.tlb"
STDMETHODIMP CXIPVCAddin::AppStartUp( VARIANT_BOOL* pnComponentInitialized)
{
HRESULT hr = CoInitialize(NULL);
JQDTestCOMLib::IClass1Ptr p(__uuidof(JQDTestCOMLib::Class1));

// alternative ATL COM objet initiation
CComPtr pH;
::CoCreateInstance(__uuidof(JQDXIPCOMCS::XIPEventHandler),NULL,CLSCTX_ALL,
__uuidof(JQDXIPCOMCS::IXIPEventHandler), (void**) &pH);

The following are the workaround until this problem can be solved:
HINSTANCE h=ShellExecute(NULL,"open","C:\\XIP_GMO\\OrdAlloc.exe","c:\\MyLog.log","",SW_SHOW);

It turn out that xip.exe.config did not specify CLR runtime version and codebase for registered COM may not point to the right place. We just need to do the folowing:
1) Add <supportedRuntime version="v2.0.50727" /> in xip.exe.config
2) Create a new directory GMOCSharpCOM on app server and set CodeBase of all C# COM object to this directory. e.g. CodeBase file:///X:/GMOCSharpCOM/GMOAllocator.dll

Wednesday, June 11, 2008

T-SQL split function

This is a code based on my goole search
CREATE FUNCTION dbo.fnSplit
(
@string varchar(500),
@delimiter char(1),
@return_index int
)
RETURNS varchar(500)
AS
BEGIN
declare @pos int
declare @piece varchar(500)

if right(rtrim(@string),1) <> @delimiter
set @string = @string + @delimiter

set @pos = patindex('%'+@delimiter+'%' , @string)

declare @i int
set @i =0
while @pos <> 0
begin
set @piece = left(@string, @pos - 1)
if @i=@return_index
return cast(@piece as varchar(500))

set @string = stuff(@string, 1, @pos, '')
set @pos = patindex('%'+@delimiter+'%' , @string)
set @i=@i+1
end

RETURN null
END