|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionInstalling SSL Certificates in IIS 5.0 seems to be an easy task. One would
think to use ADSI to set the 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
Installing SSL Certificates in IIS 5.0 Programmatically involves the following tasks:
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 StoreThere 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: CAPICOMIt'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.
BackgroundRuntime 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 Custom Runtime Callable WrapperArmed 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...
Importing COM Interfaces are actually pretty easy. Interface header fileDEFINE_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[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 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 Methodsvirtual /* [local] */ HRESULT STDMETHODCALLTYPE SetData(
/* [in] */ METADATA_HANDLE hMDHandle,
/* [string][in][unique] */ LPCWSTR pszMDPath,
/* [in] */ PMETADATA_RECORD pmdrMDData) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetData(
/* [in] */ METADATA_HANDLE hMDHandle,
/* [string][in][unique] */ LPCWSTR pszMDPath,
/* [out][in] */ PMETADATA_RECORD pmdrMDData,
/* [out] */ DWORD *pdwMDRequiredDataLen) = 0virtual HRESULT STDMETHODCALLTYPE AddKey(
/* [in] */ METADATA_HANDLE hMDHandle,
/* [string][in][unique] */ LPCWSTR pszMDPath
.NET Methodsvoid SetData(IntPtr hMDHandle,
[MarshalAs(UnmanagedType.LPWStr)] String pszMDPath,
ref METADATA_RECORD pmdrMDData); void GetData(IntPtr hMDHandle,
[MarshalAs(UnmanagedType.LPWStr)] String pszMDPath,
[MarshalAs(UnmanagedType.Struct)] ref METADATA_RECORD pmdrMDData,
out UInt32 pdwMDRequiredDataLen);// 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
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. MetaData MarshalingCOM Interop works with unmanaged memory. Effective use of
From Managed Code to Unmanaged CodeString MetaDataWindows 2000 uses Unicode strings (2 bytes per character) and the
stringData += '\0';
metaDataRecord.dwMDDataLen =
(UInt32)Encoding.Unicode.GetByteCount(stringData);
metaDataRecord.pbMDData = Marshal.StringToCoTaskMemUni(stringData);
Binary MetaDataMarshalling binary MetaData is simple. Use the metaDataRecord.dwMDDataLen = (UInt32)binaryData.Length;
metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(binaryData.Length);
Marshal.Copy(binaryData, 0, metaDataRecord.pbMDData,
(int)metaDataRecord.dwMDDataLen);
MultiSz MetaDataMultiSz 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 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 MetaDataDWORD MetaData can be marshaled by allocating 4 bytes of unmanaged memory and
calling the metaDataRecord.dwMDDataLen = (uint)Marshal.SizeOf(typeof(UInt32));
metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(
(int)metaDataRecord.dwMDDataType);
Marshal.WriteInt32(metaDataRecord.pbMDData, uintData);;
Freeing Unmanged MemoryAlways use finally blocks to free unmanaged memory. finally
{
if(metaDataRecord.pbMDData != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(metaDataRecord.pbMDData);
}
}
Using the codeThe 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 WrapperUsing the MSAdminBaseClass (MSAdminBase.dll) to programatically install SSL certificates is simple. Note: The CAPICOM dll must be registered for this sample to work.
// 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
The SSLCertHash entry is a binary MetaData type and requires a
certificate thumbprint as a // // 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
' 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 InterestThe COM Interop wrapper can be customized to support any History
| ||||||||||||||||||||