Tuesday, May 12, 2009

IE Hosted Controls - More manifests!

Arrg - back into the world of barely documented manifests.

This time, I need to create a managed control in IE with full trust. This allows you to simply create an activeX "like" control in C#, as detailed here:

http://stackoverflow.com/questions/299323/creating-and-deploying-an-activex-control-in-net


Getting full trust for the site isn't terribly hard, if you read the following posts from Shawn @ Microsoft - basically, you have to create application and deployment manifests, host them on the site using the control, and reference them in your HTML:

http://blogs.msdn.com/shawnfa/archive/2008/01/24/manifested-controls-redux.aspx
http://blogs.msdn.com/shawnfa/archive/2007/03/09/manifests-for-ie-hosted-controls.aspx

Here's my recipe for creating app and deployment manifests for an IE hosted control using Mage:

** update the application manifest with the assemblies hash **
1. mage -u myControl.dll.manifest

2. Open app manifest in notepad, and remove "extra" <dependency> tags such as osVersionInfo, and prerequisite

** sign the app manifest **
3. mage -Sign myControl.dll.manifest -CertHash 41b338fef3747f53e23929b428385ce9ccf9b891


** build the Base64 encoded SHA1 hash of the app manifest (source for hash.exe is below) **
4. hash -file myControl.dll.manifest

** Put hash into deployment manifest **
5. Open myControl.control in notepad, alter <dsig:DigestValue> with value computed in 4.

** update the deployment manifest **
6. mage -u myControl.control

** sign deployment manifest **
7. mage -Sign myControl.control -CertHash 41b338fef3747f53e23929b428385ce9ccf9b891

There are a couple of gotchas in the instructions, mainly because Mage was built for ClickOnce deployments, and not necessarily for hosted controls. In many cases if you build a "new" manifest using mage (rather than use the examples from Shawn's blog), you'll get the right information, but if you update manifests with the command line, several things will be missing in the final manifests -


1) When updating, and signing, the application manifest as detailed above, the Mage tool doesn't update the publisherIdentity associated with the signing certificate, or the assembly version (but it does update the assembly's hash). You'll need to manually update the version, update the RDN in the publisher identity (to match your signing certificate's RDN and issuerKeyHash).

2) When updating, and signing the deployment manifest (e.g. foobar.control), the Mage tool doesn't update the publisherIdentity, nor does it update the hash of the application manifest (addressed in step 4 and 5 above).


** Fixing the publisher Identity **

The publisherIdentity looks like this in the application and deployment manifests - its composed of the Distinguished Name of the certificate, and the hash of the public key of the issuer of the signing certificate :

<publisherIdentity name="CN=multipart, OU=RDN" issuerKeyHash="1d394e8e3ab5e9f1fb9921b3431543e288810066" />

If it is not correct, then your IEDebugLog will show the certificate is untrusted, and you will not be able to elevate:

Microsoft.IE.ApplicationManifest: Signing certificate trust level: Untrusted

Microsoft.IE.DeploymentManifest: Attempt to elevate CAS permissions by an untrusted control was blocked.


Getting the DN is easy, just open certmgr.msc (a MMC snapin), and copy the subject for the signing certificate into the publisherIdentity (adding in comma's). Getting the issuerKeyHash is a bit harder, and requires code, provided below. You basically take the certificate for the CA which issued the signing certificate, and build a SHA1 hash of the public key (encoded in a HEX string).

** HASH.EXE **

The partial code below gets the issuerKeyHash from a certificate file, and is also used for getting the SHA1 hash of the application manifest.


static private void fileHash(String filename)
{
byte[] hash;
SHA1Managed sha = new SHA1Managed();
FileStream strm = null;

try
{
strm = new FileStream(filename, FileMode.Open, FileAccess.Read);
hash = sha.ComputeHash(strm);
}
finally
{
if (strm != null)
strm.Close();
}
Console.Write(Convert.ToBase64String(hash));
}

protected static string[] HexStringValueArray = {
"00", "01", ...., "FD", "FE", "FF" };

public static string DataToHex(byte[] data)
{
char[] list = new char[data.Length * 2];
for (int k = 0; k < data.Length; ++k)
{
list[k * 2 + 0] = HexStringValueArray[data[k]][0];
list[k * 2 + 1] = HexStringValueArray[data[k]][1];
}
return new string(list);
}

static void certPublicKeyHash(String certificateFileName)
{
byte[] hash;
SHA1Managed sha = new SHA1Managed();
X509Certificate2 cert = new X509Certificate2(certificateFileName);
hash = sha.ComputeHash(cert.PublicKey.EncodedKeyValue.RawData);
Console.Write(DataToHex(hash));
}