Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

IIS Admin Base Object Wrapper for installing SSL Certificates

Rate me:
Please Sign up or sign in to vote.
4.57/5 (7 votes)
19 Feb 20046 min read 139.1K   1.3K   17   30
A COM Interop Wrapper for the IIS Admin Base Object that can be used to programmatically install SSL Certificates in IIS 5.0

Image 1

Introduction

Installing SSL Certificates in IIS 5.0 seems to be an easy task. One would think to use ADSI to set the SSLCertHash property using Adsutil.vbs or System.DirectoryServices in C#. Although that approach seems logical, one quickly remembers they are using a Microsoft product and discovers that the schema incorrectly specifies the SSLCertHash property as an expanded null terminated string, instead of as binary data. A quick search of the MS KB will pull up HOW TO: Programmatically Install SSL Certificates for Internet Information Server (IIS) confirming this little idiosyncrasy with ADSI. Great, a COM only interface (IMSAdminBase) that is accessible through C/C++ is what Microsoft leaves us to solve this little task. Sounds nasty.

Using the Microsoft .NET Framework it's possible to create a COM callable wrapper/runtime callable wrapper (RCW) to allow VBScript and C# to use the IMSAdminBase interface. My COM knowledge is very limited and messing with C was the last thing I wanted to do, so I decided see what COM Interop in the .NET Framework was all about.

Installing SSL Certificates in IIS 5.0 Programmatically involves the following tasks:

  • Generate/Load Certificate into Local Computer Certificate Store
  • Get the Certificate Thumbprint
  • Set the SSLCertHash and SSLStore Name Metabase Properties

The attached solution contains a C# sample tool and a VBScript sample for installing SSL Certificates using the custom COM callable wrapper/runtime callable wrapper (RCW).

Generating SSL Certificates and the Certificate Store

There are numerous ways to get a SSL Certificate for IIS. This article only covers generating a self-signed certificate. Included in the .NET Framework SDK and the Platform SDK is a tool called makecert.exe that works great for generating fake (self-signed) certificates.

IIS SSL Certificates need the following parameters:

makecert.exe -a SHA1 -ss my -sr LocalMachine -n "CN="%ComputerName% -b 01/01/2000 -e 01/01/2050 -eku 1.3.6.1.5.5.7.3.1 -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12

The certificate's subject name and expiration date are configurable. The full subject name can include "CN=Name,OU=Container,O=Company,L=City,S=State,C=Country". The -ss my -sr LocalMachine switches save the generated certificate to the Personal Certificate Store (MY) for the Local Computer. If you import your own certificate, make sure its stored there.

Platform SDK Redistributable: CAPICOM

It's possible to access the Local Machine Certificate Store using the CryptoAPI but I found using the CAPICOM COM client to be much easier. You can download the CAPICOM library from the Microsoft download site.

To install CAPICOM extract "CAPICOM.DLL" from CAPICOM.CAB to your system32 directory, then execute "regsvr32.exe CAPICOM.DLL". The debug symbols should also be copied to the system32 directory for running C# projects in debug mode.

Background

Runtime callable wrappers are used by .NET to access COM components. MSDN has a great deal of documentation on the subject. I would suggest reading some articles over there if you are interested. You may have of used RCWs without noticing, Visual Studio .NET generates RCWs for you when add a COM Reference to a project. Alternatively, you could use the Type Library Importer (Tlbimp.exe) tool to generate a wrapper from a type library file (TLB). So where is the IIS Admin Base Object in the COM Reference list? Luckily, our friends at Microsoft don't seem to ship a TLB for the IMSAdminBase Interface. The next step involved searching the Platform SDK for any trace of Interface Definition Language (IDL) source. IDL source can be compiled by the MIDL compiler command-line tool to generate the type library file (TLB). Compiling the IDL quickly becomes difficult when the Platform SDK only provides C header files for the IMSAdminBase Interface.

Custom Runtime Callable Wrapper

Armed with the Iadmw.h header file I started to create a custom RCW. The MSAdminBase project contains the COM Interop wrappers. All Interop projects begin with...

using System.Runtime.InteropServices;

Importing COM Interfaces are actually pretty easy.

Interface header file

C#
DEFINE_GUID(CLSID_MSAdminBase_W, 0xa9e69610, 0xb80d, 0x11d0, 
  0xb9, 0xb9, 0x0, 0xa0, 0xc9, 0x22, 0xe7, 0x50); 
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("70B51430-B6CA-11d0-B9B9-00A0C922E750")
IMSAdminBaseW : public IUnknown 
{
        ...Interface Methods...
END_INTERFACE
    }

.NET Wrapper

C#
[ComImport, Guid("a9e69610-b80d-11d0-b9b9-00a0c922e750")]
public class MSAdminBase
{}
[ComImport, Guid("70B51430-B6CA-11d0-B9B9-00A0C922E750"), 
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMSAdminBase
{
...Interface Methods 
}

The CLSID requires a public class with no constructor. The interface requires implementing IUnknown.

The more time consuming aspect of this process is wrapping the Interface methods. I did find out that you don't have to fully wrap the entire method signature, but all the methods need to be declared sequentially in the C# interface wrapper.

Interface Methods

C#
virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetData(
/* [in] */ METADATA_HANDLE hMDHandle,
/* [string][in][unique] */ LPCWSTR pszMDPath,
/* [in] */ PMETADATA_RECORD pmdrMDData) = 0;
C#
virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetData(
/* [in] */ METADATA_HANDLE hMDHandle, 
/* [string][in][unique] */ LPCWSTR pszMDPath, 
/* [out][in] */ PMETADATA_RECORD pmdrMDData,
 /* [out] */ DWORD *pdwMDRequiredDataLen) = 0
C#
virtual HRESULT STDMETHODCALLTYPE AddKey(
/* [in] */ METADATA_HANDLE hMDHandle, 
/* [string][in][unique] */ LPCWSTR pszMDPath

.NET Methods

C#
void SetData(IntPtr hMDHandle,
[MarshalAs(UnmanagedType.LPWStr)] String pszMDPath,
ref METADATA_RECORD pmdrMDData); 
C#
void GetData(IntPtr hMDHandle,
[MarshalAs(UnmanagedType.LPWStr)] String pszMDPath,
[MarshalAs(UnmanagedType.Struct)] ref METADATA_RECORD pmdrMDData, 
out UInt32 pdwMDRequiredDataLen);
C#
// Skipped
void AddKey();

The MarshalAs attribute can be used to tell the CLR how to Marshal objects between .NET and COM. It is not always required but does help when looking at the hungarian notation variable names. All structs used by the interface methods also need a wrapper.

All Constants, Enums, and Structs are defined in IIScnfg.h. The METADATA_RECORD structure contains information about a metabase entry. It is used as an input parameter by the SetData method and as an input/output parameter by methods that retrieve data from the metabase in GetData.

C#
typedef struct _METADATA_RECORD {
DWORD dwMDIdentifier;
DWORD dwMDAttributes;
DWORD dwMDUserType;
DWORD dwMDDataType;
DWORD dwMDDataLen;
unsigned char *pbMDData;
DWORD dwMDDataTag; }

I highly recommend looking at the NET Framework Developer's Guide (VS.NET Help): COM Data Types. DWORDs are a UInt32 and unsigned char * is a IntPtr.

MetaData Marshaling

COM Interop works with unmanaged memory. Effective use of try..finally blocks can help with resource cleanup. All memory allocated with the Marshal class and open handles must be manually freed, the CLR garbage collector will not do it for you. Since the METADATA_RECORD structure contains a pointer to MetaData, all metabase entry data types will need to be marshaled to unmanaged memory before calling the Interface method.

From Managed Code to Unmanaged Code

String MetaData

Windows 2000 uses Unicode strings (2 bytes per character) and the METADATA_RECORD structure requires the MetaData length field to include the null-terminated character.

C#
stringData += '\0';
metaDataRecord.dwMDDataLen = 
  (UInt32)Encoding.Unicode.GetByteCount(stringData);
metaDataRecord.pbMDData = Marshal.StringToCoTaskMemUni(stringData);
Binary MetaData

Marshalling binary MetaData is simple. Use the Marshal.Copy method.

C#
metaDataRecord.dwMDDataLen = (UInt32)binaryData.Length;
metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(binaryData.Length);
Marshal.Copy(binaryData, 0, metaDataRecord.pbMDData, 
  (int)metaDataRecord.dwMDDataLen);
MultiSz MetaData

MultiSz MetaData is marshaled as a string array of null-terminated strings that has final null-terminated character after the last element. I had trouble with the null-terminated characters so I marshalled this data type as binary MetaData

C#
ArrayList multiSzData = new ArrayList();
foreach(string stringData in stringArrayData)
{
// (Add null// (Add null terminated) multiSzData.AddRange(
  Encoding.Unicode.GetBytes(stringData + '\0'));
}// (Add null terminated) multiSzData.AddRange(new byte[2]{0x00,0x00});
binaryData = (byte[])multiSzData.ToArray(Type.GetType("System.Byte"));
// Allocate Binary Data Memory 
  metaDataRecord.dwMDDataLen = (UInt32)binaryData.Length;
metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(binaryData.Length);
// Copy Binary Data to Unmanaged Memory
// Copy Binary Data to Unmanaged Memory 
Marshal.Copy(binaryData, 0, metaDataRecord.pbMDData, 
  (int)metaDataRecord.dwMDDataLen);
DWORD MetaData

DWORD MetaData can be marshaled by allocating 4 bytes of unmanaged memory and calling the Marshal.WriteInt32 method.

C#
metaDataRecord.dwMDDataLen = (uint)Marshal.SizeOf(typeof(UInt32));
            metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(
  (int)metaDataRecord.dwMDDataType);
Marshal.WriteInt32(metaDataRecord.pbMDData, uintData);;

Freeing Unmanged Memory

Always use finally blocks to free unmanaged memory.

C#
finally
{
if(metaDataRecord.pbMDData != IntPtr.Zero)
{
     Marshal.FreeCoTaskMem(metaDataRecord.pbMDData);
}
}

Using the code

The COM Interop project MSAdminBase can be used as runtime callable wrapper for .NET projects or a COM callable wrapper for VB/VBScript projects.

C# - Using the Runtime Callable Wrapper

Using the MSAdminBaseClass (MSAdminBase.dll) to programatically install SSL certificates is simple. Note: The CAPICOM dll must be registered for this sample to work.

  1. Declare the namespace and Add a reference to either the project or the compiled dll interop.MSAdminBase.dll.
    using 
    Windows.Services.Iis.Metabase; 
  2. Instantiate a new MSAdminBaseClass
  3. Call the MSAdminBaseClass.SetMetabaseData method for the SSLCertHash (5506) metabase entry and the SSLStoreName (5511) metabase entry.
C#
// Open Metabase Interface
MSAdminBaseClass adminBaseClass = new MSAdminBaseClass();
// Set SSL Certificate
adminBaseClass.SetMetabaseData(SslCertHashId,
  metaDataPath, thumbprintByteArray);
adminBaseClass.SetMetabaseData(SslStoreNameId, metaDataPath, "MY");

The method signature for SetMetabaseData is

public void SetMetabaseData(uint metabaseDataId, string 
metabaseDataPath, object data) 

The SSLCertHash entry is a binary MetaData type and requires a certificate thumbprint as a byte[]. The CAPICOM COM library includes a few useful classes to convert from a thumbprint for hex string to a byte[]ode>.

C#
// // Get Hex String Thumbprint
string hexThumbprint = certificate.Thumbprint;
Console.WriteLine("SSL Certificate Thumbprint: " + hexThumbprint);
// Convert Hex String to Byte[] Utilities certUtilities = new Utilities();
string binaryThumbprint = certUtilities.HexToBinary(hexThumbprint);
thumbprintByteArray = (byte[])certUtilities.BinaryStringToByteArray(
  binaryThumbprint);

The SSLStoreName entry in a string MetaData type and should always be "MY" for IIS SSL Certificates.

VBScript - Using the COM Callable Wrapper

  1. Copy interop.MSAdminBase.dll to the system32 directory and run RegAsm.exe interop.MSAdminBase.dll /tlb:interop.MSAdminBase.tlb
  2. CreateObject("IIS.MSAdminBase")
  3. Call the MSAdminBaseClass.SetMetabaseData method for the SSLCertHash (5506) metabase entry and the SSLStoreName (5511) metabase entry.
VB.NET
' Open Metabase
Dim metaBase
Set metaBase = CreateObject("IIS.MSAdminBase")
' Set SSL Certificate
metaBase.SetMetabaseData SSLCertHashId, "/W3SVC/1", thumbprintByteArray
metaBase.SetMetabaseData SSLStoreNameId, "/W3SVC/1", SSLStoreName

Points of Interest

The COM Interop wrapper can be customized to support any

IMSAdminBase 
method. I only implemented the methods required to install SSL Certificates. ADSI should normally be used to configure the IIS Metabase but this wrapper comes in handy for the few times it's not possible.

History

  • 2/8/2004 - Initial Release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Systems Engineer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralThis is not needed - setting binary properties is very easy using System.DirectoryServices Pin
padazhdi22-Feb-07 9:25
padazhdi22-Feb-07 9:25 
GeneralRe: This is not needed - setting binary properties is very easy using System.DirectoryServices Pin
Anurodh Ora22-Mar-07 3:14
Anurodh Ora22-Mar-07 3:14 
Generaltrying to use from asp Pin
badams28-Jan-07 17:01
badams28-Jan-07 17:01 
QuestionYour Wrapper for Perl? Pin
ReEvolveD24-May-05 7:17
ReEvolveD24-May-05 7:17 
Generalpure genius ! Pin
benfr14-Jun-04 4:58
benfr14-Jun-04 4:58 
General'Compile Error: Variable uses an automation type not supported in Visual Basic Pin
nitincnarkar14-Apr-04 17:21
nitincnarkar14-Apr-04 17:21 
GeneralServer certificate Pin
Member 96985424-Mar-04 3:09
Member 96985424-Mar-04 3:09 
QuestionInterop.CAPICOM.dll Source? Pin
rkekos3-Mar-04 8:49
rkekos3-Mar-04 8:49 
AnswerRe: Interop.CAPICOM.dll Source? Pin
Karl McGuinness3-Mar-04 14:57
Karl McGuinness3-Mar-04 14:57 
AnswerRe: Interop.CAPICOM.dll Source? Pin
Karl McGuinness3-Mar-04 15:03
Karl McGuinness3-Mar-04 15:03 
GeneralRe: Interop.CAPICOM.dll Source? Pin
rkekos2-Apr-04 11:13
rkekos2-Apr-04 11:13 
What if I want to get specific information about a particular certificate using the CAPICOM dll? Meaning all I have is the thumbprint hash of a cert. Just by that I need to get the details of the actual cert from the store. Things like valid date range, issued by, etc. From what I can see only the CAPICOM class gives you these utilities but I do not see something like CAPICOM.Certificate.LoadCertFromHash( "my_cert_hash" ) function.

Karl's the man! Poke tongue | ;-P


GeneralRe: Interop.CAPICOM.dll Source? Pin
Karl McGuinness2-Apr-04 13:20
Karl McGuinness2-Apr-04 13:20 
GeneralRe: Interop.CAPICOM.dll Source? Pin
rkekos5-Apr-04 12:23
rkekos5-Apr-04 12:23 
GeneralRe: Interop.CAPICOM.dll Source? Pin
rkekos6-Apr-04 6:16
rkekos6-Apr-04 6:16 
QuestionHow to implement Pin
rkekos2-Mar-04 12:15
rkekos2-Mar-04 12:15 
AnswerRe: How to implement Pin
Karl McGuinness2-Mar-04 12:53
Karl McGuinness2-Mar-04 12:53 
GeneralRe: How to implement Pin
rkekos2-Mar-04 15:11
rkekos2-Mar-04 15:11 
GeneralRe: How to implement Pin
Karl McGuinness3-Mar-04 15:15
Karl McGuinness3-Mar-04 15:15 
GeneralRe: How to implement Pin
rkekos3-Mar-04 17:19
rkekos3-Mar-04 17:19 
GeneralRe: How to implement Pin
Karl McGuinness4-Mar-04 7:08
Karl McGuinness4-Mar-04 7:08 
GeneralRe: How to implement Pin
rkekos2-Apr-04 11:42
rkekos2-Apr-04 11:42 
GeneralRe: How to implement Pin
Karl McGuinness2-Apr-04 13:14
Karl McGuinness2-Apr-04 13:14 
GeneralRe: How to implement Pin
rkekos5-Apr-04 12:21
rkekos5-Apr-04 12:21 
GeneralRe: How to implement Pin
Karl McGuinness5-Apr-04 14:08
Karl McGuinness5-Apr-04 14:08 
GeneralRe: How to implement Pin
rkekos6-Apr-04 6:14
rkekos6-Apr-04 6:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.