|
|||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||
|
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". I used %ComputerName% to generate a certificate with a subject name that uses the 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 often used RCWs without noticing. Visual Studio .NET generates RCWs for you when you add a COM Reference to a project. Alternatively, you could use 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... using System.Runtime.InteropServices;
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 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) = 0 virtual 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 All constants, 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 COM Data Types (VS.NET Help). 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 marshaled this data type as binary MetaData. ArrayList multiSzData = new ArrayList();
foreach(string stringData in stringArrayData)
{
// (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
Marshal.Copy(binaryData, 0, metaDataRecord.pbMDData,
(int)metaDataRecord.dwMDDataLen);
DWORD MetaData
metaDataRecord.dwMDDataLen = (uint)Marshal.SizeOf(typeof(UInt32));
metaDataRecord.pbMDData =
Marshal.AllocCoTaskMem((int)metaDataRecord.dwMDDataType);
Marshal.WriteInt32(metaDataRecord.pbMDData, uintData);
Freeing Unmanaged MemoryAlways use finally
{
if(metaDataRecord.pbMDData != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(metaDataRecord.pbMDData);
}
}
Using the codeThe COM Interop project MSAdminBase can be used as a runtime callable wrapper for .NET projects or a COM callable wrapper for VB/VBScript projects. C# - Using the Runtime Callable WrapperUsing the
// Open Metabase Interface
MSAdminBaseClass adminBaseClass = new MSAdminBaseClass();
// Set SSL Certificate
adminBaseClass.SetMetabaseData(SslCertHashId, metaDataPath,
thumbprintByteArray);
adminBaseClass.SetMetabaseData(SslStoreNameId, metaDataPath, "MY");
The method signature for public void SetMetabaseData(uint metabaseDataId,
string metabaseDataPath, object data)
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 is 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 History2/8/2004 - Initial release.
|
||||||||||||||||||||||||||||||||||||||||||||