65.9K
CodeProject 正在变化。 阅读更多。
Home

用于安装 SSL 证书的 IIS 管理基对象包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (9投票s)

2004年2月13日

6分钟阅读

viewsIcon

65530

downloadIcon

1460

一个用于 IIS Admin Base Object 的 COM 互操作包装器,可用于以编程方式安装 IIS 5.0 中的 SSL 证书。

MSAdminBase.jpg

引言

在 IIS 5.0 中以编程方式安装 SSL 证书似乎是一项简单的任务。人们可能会认为可以使用 ADSI 使用 Adsutil.vbs 或 C# 中的 System.DirectoryServices 来设置 SSLCertHash 属性。虽然这种方法看起来很合乎逻辑,但很快就会想起您使用的是 Microsoft 产品,并发现架构错误地将 SSLCertHash 属性指定为扩展的空终止字符串,而不是二进制数据。快速搜索 MS KB 会找到 如何:为 Internet 信息服务 (IIS) 以编程方式安装 SSL 证书,这证实了 ADSI 的这个小怪癖。太好了,Microsoft 为我们解决这个小任务提供了一个只能通过 C/C++ 访问的 COM 接口(IMSAdminBase)。听起来很棘手。

使用 Microsoft .NET Framework,可以创建一个 COM 可调用包装器/运行时可调用包装器 (RCW),以允许 VBScript 和 C# 使用 IMSAdminBase 接口。我对 COM 的了解非常有限,而且我最不想做的就是弄 C,所以我决定看看 .NET Framework 中的 COM 互操作究竟是什么。

以编程方式在 IIS 5.0 中安装 SSL 证书涉及以下任务

  • 将证书生成/加载到本地计算机证书存储中
  • 获取证书缩略图
  • 设置 SSLCertHash 和 SSLStore Name 元数据库属性

随附的解决方案包含一个 C# 示例工具和一个 VBScript 示例,用于使用自定义的 COM 可调用包装器/运行时可调用包装器 (RCW) 安装 SSL 证书。

生成 SSL 证书和证书存储

有多种方法可以获取 IIS 的 SSL 证书。本文仅涵盖生成自签名证书。在 .NET Framework SDK 和 Platform SDK 中包含了一个名为 makecert.exe 的工具,该工具非常适合生成假的(自签名的)证书。

IIS SSL 证书需要以下参数

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

证书的主题名称和过期日期是可配置的。完整的主题名称可以包含“CN=Name,OU=Container,O=Company,L=City,S=State,C=Country”。我使用了 %ComputerName% 来生成一个使用 ComputerName 环境变量的主题名称的证书。-ss my -sr LocalMachine 开关将生成的证书保存在本地计算机的个人证书存储 (MY) 中。如果您导入自己的证书,请确保将其存储在此处。

Platform SDK 可再发行组件:CAPICOM

可以使用 CryptoAPI 访问本地计算机证书存储,但我发现使用 CAPICOM COM 客户端要容易得多。您可以从 Microsoft 下载站点 下载 CAPICOM 库。

要安装 CAPICOM,请将 CAPICOM.DLLCAPICOM.CAB 提取到您的 system32 目录,然后执行“regsvr32.exe CAPICOM.DLL”。调试符号也应复制到 system32 目录,以便在调试模式下运行 C# 项目。

背景

运行时可调用包装器由 .NET 用于访问 COM 组件。MSDN 提供了大量关于该主题的文档。如果您有兴趣,我建议您阅读那里的几篇文章。您可能经常在不知情的情况下使用 RCW。Visual Studio .NET 在您向项目添加 COM 引用时会自动为您生成 RCW。或者,您也可以使用类型库导入器 (Tlbimp.exe)工具从类型库文件 (TLB) 生成包装器。那么,COM 引用列表中 IIS Admin Base Object 在哪里?幸运的是,我们的 Microsoft 朋友似乎没有为 IMSAdminBase 接口提供 TLB。下一步是搜索 Platform SDK 中是否有接口定义语言 (IDL) 源的痕迹。IDL 源可以通过 MIDL 编译器命令行工具进行编译,以生成类型库文件 (TLB)。当 Platform SDK 只为 IMSAdminBase 接口提供 C 头文件时,编译 IDL 会很快变得困难。

自定义运行时可调用包装器

有了 Iadmw.h 头文件,我开始创建一个自定义 RCW。MSAdminBase 项目包含 COM 互操作包装器。所有互操作项目都以...开始

using System.Runtime.InteropServices;

导入 COM 接口实际上非常简单。

接口头文件

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 包装器

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

CLSID 需要一个公共类,并且没有构造函数。该接口需要实现 IUnknown

这个过程中更耗时的是包装接口方法。我确实发现您不必完全包装整个方法签名,但所有方法都需要按顺序在 C# 接口包装器中声明。

接口方法

virtual /* [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 方法

void 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();

MarshalAs 属性可用于告知 CLR 如何在 .NET 和 COM 之间进行对象封送。它并非总是必需的,但在查看匈牙利命名法变量名时会很有帮助。接口方法使用的所有 structs 也需要一个包装器。

所有常量、enums 和 structs 都在 IIScnfg.h 中定义。METADATA_RECORD 结构包含有关元数据库条目的信息。它用作 SetData 方法的输入参数,以及用于从 GetData 方法中检索元数据库数据的输入/输出参数。

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

我强烈建议查看 NET Framework Developer's Guide COM Data Types (VS.NET Help)。DWORD 是一个 UInt32,而无符号 char * 是一个 IntPtr

元数据封送

COM 互操作处理非托管内存。有效使用 try..finally 块有助于资源清理。使用 Marshal 类分配的所有内存和打开的句柄必须手动释放,CLR 垃圾回收器不会为您代劳。由于 METADATA_RECORD 结构包含指向 MetaData 的指针,因此所有元数据库条目数据类型在调用接口方法之前都需要封送到非托管内存。

从托管代码到非托管代码

字符串元数据

Windows 2000 使用 Unicode 字符串(每个字符 2 字节),并且 METADATA_RECORD 结构要求 MetaData 长度字段包含空终止字符。

stringData += '\0';
metaDataRecord.dwMDDataLen = (UInt32)Encoding.Unicode.GetByteCount(stringData);
metaDataRecord.pbMDData = Marshal.StringToCoTaskMemUni(stringData);
二进制元数据

封送二进制元数据很简单。使用 Marshal.Copy 方法。

metaDataRecord.dwMDDataLen = (UInt32)binaryData.Length;
metaDataRecord.pbMDData = Marshal.AllocCoTaskMem(binaryData.Length);
Marshal.Copy(binaryData, 0, metaDataRecord.pbMDData,
    (int)metaDataRecord.dwMDDataLen);
MultiSz 元数据

MultiSz 元数据被封送为一系列空终止字符串,在最后一个元素之后有一个最终的空终止字符。我在空终止字符上遇到了麻烦,所以我将此数据类型封送为二进制元数据。

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 元数据

DWORD 元数据可以通过分配 4 字节的非托管内存并调用 Marshal.WriteInt32 方法来封送。

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

释放非托管内存

始终使用 finally 块来释放非托管内存。

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

使用代码

COM 互操作项目 MSAdminBase 可用作 .NET 项目的运行时可调用包装器,也可作为 VB/VBScript 项目的 COM 可调用包装器。

C# - 使用运行时可调用包装器

使用 MSAdminBaseClassMSAdminBase.dll)以编程方式安装 SSL 证书非常简单。注意:必须注册 CAPICOM DLL 才能使此示例正常工作。

  1. 声明命名空间并将引用添加到项目或已编译的 DLL interop.MSAdminBase.dll
    using Windows.Services.Iis.Metabase;
  2. 实例化一个新的 MSAdminBaseClass
  3. 调用 MSAdminBaseClass.SetMetabaseData 方法,分别用于 **SSLCertHash** (5506) 元数据库条目和 **SSLStoreName** (5511) 元数据库条目。
// Open Metabase Interface
MSAdminBaseClass adminBaseClass = new MSAdminBaseClass();
// Set SSL Certificate
adminBaseClass.SetMetabaseData(SslCertHashId, metaDataPath,
    thumbprintByteArray);
adminBaseClass.SetMetabaseData(SslStoreNameId, metaDataPath, "MY");

SetMetabaseData 方法签名是

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

**SSLCertHash** 条目是二进制元数据类型,需要证书缩略图作为 byte[]。CAPICOM COM 库包含一些有用的类,可以将十六进制字符串的缩略图转换为 byte[]

// 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);

**SSLStoreName** 条目是字符串元数据类型,对于 IIS SSL 证书应始终为“MY”。

VBScript - 使用 COM 可调用包装器

  1. interop.MSAdminBase.dll 复制到 system32 目录并运行 RegAsm.exe interop.MSAdminBase.dll /tlb:interop.MSAdminBase.tlb
  2. CreateObject("IIS.MSAdminBase")
  3. 调用 MSAdminBaseClass.SetMetabaseData 方法,分别用于 **SSLCertHash** (5506) 元数据库条目和 **SSLStoreName** (5511) 元数据库条目。
' Open Metabase
Dim metaBase
Set metaBase = CreateObject("IIS.MSAdminBase")
' Set SSL Certificate
metaBase.SetMetabaseData SSLCertHashId, "/W3SVC/1", thumbprintByteArray
metaBase.SetMetabaseData SSLStoreNameId, "/W3SVC/1", SSLStoreName

关注点

COM 互操作包装器可以定制以支持任何 IMSAdminBase 方法。我只实现了安装 SSL 证书所需的方法。通常应使用 ADSI 配置 IIS 元数据库,但此包装器在少数无法使用 ADSI 的情况下非常有用。

历史

2004 年 2 月 8 日 - 首次发布。

© . All rights reserved.