以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 XML基础 』  (http://bbs.xml.org.cn/list.asp?boardid=1)
----  使用 ASP.NET 实现 XML 密钥管理服务  (http://bbs.xml.org.cn/dispbbs.asp?boardid=1&rootid=&id=8925)


--  作者:diy930
--  发布时间:7/21/2004 2:01:00 PM

--  使用 ASP.NET 实现 XML 密钥管理服务
简介
本文及其相关示例代码讲述创建符合基于 SOAP 消息的接口规范的 Microsoft ASP.NET Web 服务。具体地说,接口由 Web 服务描述语言 (WSDL) 文档定义,WSDL 文档定义了用于与服务通讯的 SOAP 消息和 XML 类型。人们对基于 XML 的 Web 服务的兴趣的日益增长表示将有许多使用 WSDL 定义的标准服务。这将为构建通过公共客户端实现可访问的兼容服务实现提供基础。

在本文中,我们研究一个这样的 Web 服务标准:XML 密钥管理规范 (XKMS)。XKMS 定义了支持信任服务和发现服务的接口,用于公钥加密安全解决方案。这些接口包括支持公钥注册/撤消、密钥漫游、密钥恢复、实体公钥定位和实体公钥验证。

如果您熟悉 ASP.NET Web 服务,将发现大部分讨论都从构建一个新的 Web 服务的角度开始。本文假设您从编写服务应用程序代码和定义对公共公开的方法开始。然后通过服务实现使用 ASP.NET 工具自动发布 WSDL 描述、创建客户端代理代码等等。用于在客户端和服务应用程序之间通讯的 SOAP 消息格式将符合 WSDL 文档,但您通常并不关心这些消息格式具体是什么。它们与实现的代码一致就足够了。

必须构建一个符合预定义 SOAP 消息的 Web 服务实现为您(作为开发人员)带来其他困难。这要求您仔细开发 .NET 对象模型,并使用 XML 序列化属性以确保一致的 SOAP 消息。要手动完成该操作,从 WSDL 文档开始,需要对 WSDL、SOAP、XML 架构和 ASP.NET SOAP 序列化功能有相当深入的了解。虽然许多开发人员也许了解这些技术,但它仍然代表很大的负担,而 ASP.NET 工具可以明显减少这些负担。

下一步,我们将使用 ASP.NET Web 服务工具逐步创建与 XKMS 兼容的服务和客户端,向您说明如何:

• 从 WSDL 开始创建对象模型

• 创建公开公共服务接口的原型代码

• 从 WSDL 创建客户端服务代理

然后我们将向您说明如何创建 XKMS 服务和客户端应用程序,突出显示 WSDL 和 ASP.NET 工具中一些有趣的限制并讨论解决这些问题的方法。

返回页首
创建 XKMS 服务
生成服务原型代码
生成 XKMS 服务的第一步是从 XKMS 规范获取 XKMS WSDL 文档。对于许多服务来说,WSDL 文档会包含所有需要的 XML 架构信息。但是,XKMS 导入由 W3C/IETF XML 签名规范定义的 XML 架构。因为当前的 ASP.NET wsdl 实用工具不会对导入的架构根据它们的 URL 进行自动检索,您需要手动检索该文档。

Wsdl 实用工具可以使用 WSDL 和 XML 架构文档并生成相符合的服务原型代码。但是,如果您试图用参考规范中包含的 WSDL 和 XML 架构文档的版本执行这一工作,wsdl 会因为过期的 XML 架构用法而返回错误。这只不过反映了包括 XML 架构标准在内的 XML 标准快速发展的特点,以及 Microsoft .NET 框架最近的发展。

幸运的是,这个问题很容易解决。解决方案是使 XKMS WSDL 架构和 XML 签名架构文档都符合最新的 XML 架构规范。这需要把这些文档中所有 XML 架构命名空间的实例更新为 "http://www.w3.or/2001/XMLSchema",并把 "type" 定义更新为与最新架构规范相匹配。为了我们的目的,这实际上是进行了下面的更改:

• integer 更改为 int

• timeInstant 更改为 dateT

• uriReference 更改为 anyURI

(注:XKMS WSDL 和 XML 签名架构的未来版本应该符合 2001 XML 架构规范。)

更新的 XKMS WSDL 文档和 XML 签名架构可在如下位置找到:

• XSchema\xkms.wsdl

• XSchema\w3c_xml_signature.xsd

要生成服务原型代码,运行命令(参见 XSchema\Autogen.bat):

wsdl /server /out:XKMSProto.cs xkms.wsdl w3c_xml_signature.xsd

该命令的输出可在示例代码文件 XSchema\XKMSProto.cs 中找到。如果查看该文件,会发现它包含一个包含服务公共公开方法的抽象类。它还包含提供对象模型的 C# 类型定义,该对象模型从 XKMS 使用的 XML 类型中派生。对该文件进行一次快速浏览将足以使您相信使用 wsdl 实用工具生成此代码的价值。从 WSDL 和 XML 架构文档手动创建这些代码或等价的代码将既费时又复杂。

XKMS 对象模型
现在让我们研究一下由 wsdl 实用工具生成的对象模型。特别是,我们应该研究一些更有趣的设计模式和地方,在其中 WSDL 限制影响了实现。

首先,您会注意到简单 XML 类型(int、string、base64Binary 等)对象的生成非常简单。这些类型直接映射为 .NET 公共语言运行库 (CLR) 类型(System.Int32、System.String、System.Byte[] 等)。Wsdl 实用工具只是创建一个适当类型的字段,该字段的名称与 XML 元素或属性的名称对应。枚举类型的生成也同样简单。

然而,当面对复杂的 XML 类型时,事情要更复杂一点。这里的问题是,对于给定的实例,当这些类型可能有可变数目和类型的子元素时,如何表示它们,。Wsdl 实用工具使用两种基本设计模式处理该问题:

• 对于定义为子元素序列的 XML 复杂类型,生成一个 C# 类,每个可能的子元素在该 C# 类中有一个对应的字段。每个字段的类型由它的 XML 的类型决定。使用字段以类似的方法处理属性子元素。KeyBindingType 类显示了这样的示例。通过把一个字段的值设置为空,已序列化的窗体中将没有对应的 XML 元素。

• 对于定义为子元素选择的 XML 复杂类型,需要的方法稍有不同,如 KeyInfo 类所示。这种情况下,创建一个 object[] 类型的字段 (Item) 用于保持对子元素的引用。如果多个子元素的 .NET CLR 类型(例如,String、Byte[])相同,还创建相关联的枚举数组字段 (ItemsElementName)。该字段在每个数组位置中保持 XML 类型的描述,并确保只能指定对该类合法的 XML 类型。如果每个可能的子元素是不同的类型,那么就不需要像 KeyValue 类中所示的枚举了。

对于选择语义定义的复杂 XML 类型,wsdl 生成的对象有一个潜在的问题。这是由当前 wsdl 实用工具的限制造成的。只有当复杂类型允许“任何”元素作为有效子元素时才会出现这个问题。多个 XKMS 类型会出现这个问题,例如 KeyInfo。生成 C# 类的 Wsdl 实用工具在 object/object[] 数组字段上放置一个 XmlAnyElementAttribute,以表示允许“任何”。如果有相关联的枚举类型,它会有一个 'Item' 的值作为它的一个成员。对于以编程方式处理“任何”值,这不是最好的构造,而且,生成的代码在运行时会引发序列化异常。

更正此问题并不困难,不过需要手动编辑。首先,从相关联的 object/object[] 字段上移除 XmlAnyElementAttribute,并把它放置在类型为 XmlElement 的新字段上。使用 KeyInfo 类作为示例,自动生成的代码:

public class KeyInfo {
    /// <remarks/>
    [System.Xml.Serialization.XmlAnyElementAttribute()]
    [System.Xml.Serialization.XmlElementAttribute("KeyName", typeof(string))]
    [System.Xml.Serialization.XmlElementAttribute("KeyValue", typeof(KeyValue))]
    [System.Xml.Serialization.XmlElementAttribute("RetrievalMethod",
         typeof(RetrievalMethod))]
    [System.Xml.Serialization.XmlElementAttribute("X509Data", typeof(X509Data))]
    [System.Xml.Serialization.XmlElementAttribute("PGPData", typeof(PGPData))]
    [System.Xml.Serialization.XmlElementAttribute("SPKIData", typeof(string))]
    [System.Xml.Serialization.XmlElementAttribute("MgmtData", typeof(string))]
    [System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")]
    public object[] Items;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("ItemsElementName")]
    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public ItemsChoiceType2[] ItemsElementName;
    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute(DataType="ID")]
    public string Id;
}

被更改为:

public class KeyInfo {
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("KeyName", typeof(string))]
    [System.Xml.Serialization.XmlElementAttribute("KeyValue", typeof(KeyValue))]
    [System.Xml.Serialization.XmlElementAttribute("RetrievalMethod",
         typeof(RetrievalMethod))]
    [System.Xml.Serialization.XmlElementAttribute("X509Data", typeof(X509Data))]
    [System.Xml.Serialization.XmlElementAttribute("PGPData", typeof(PGPData))]
    [System.Xml.Serialization.XmlElementAttribute("SPKIData", typeof(string))]
    [System.Xml.Serialization.XmlElementAttribute("MgmtData", typeof(string))]
    [System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")]
    public object[] Items;
    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("ItemsElementName")]
    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public ItemsChoiceType2[] ItemsElementName;
    [System.Xml.Serialization.XmlAnyElementAttribute()]
    public XmlElement[] Any;
    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute(DataType="ID")]
    public string Id;
}

然后,更改对应的枚举类型 (ItemsChoiceType2) 以便移除 'Item' 值,如下所示:

[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.w3.org/
       2000/09/xmldsig#", IncludeInSchema=false)]
public enum ItemsChoiceType2 {
    KeyName,
    KeyValue,
    RetrievalMethod,
    X509Data,
    PGPData,
    SPKIData,
    MgmtData,
}

可在 XService\bin\XKMSTypes.cs 中找到带有如上所述更改的修改过的 XKMS 对象模型。做出这些修改之后,还可以再进一步做一个简化。在 Transform 类中,您会注意到,现在 object[] Items 字段所允许的类型全部都是 String 类型。因此,我们可以把 Items 字段更改为 String[] 类型,使它更容易使用。

如果把 XKMSTypes.cs 与 XKMSProto.cs 进行比较,您将会注意到其他几个更改。最重要的是对 AuthUserInfoType 和 AuthServerInfoType 类以及它们的 Signature 类型字段(ProofOfPossession 和 KeyBindingAuth)的更改。这些字段已经更改为 String 类型和 Signature 类,而支持类(SignedInfo 和 Reference)被移除了。

这些更改是由 WSDL 和 XML 架构表达中的限制造成的,而不是由 wsdl 实用工具造成的。尽管这些类型的文档能准确描述 XML 类型和消息接口,但是无法表达类型之间的关键语法关系。对于 XKMS,WSDL 无法表明 ProofOfPossession 和 KeyBindingAuth XML 签名(在 Register 请求消息中使用)是在消息内容上计算的。具体地说,它们是在 Prototype 参数(类型为 KeyBindingType)的已序列化 XML 表示形式和 Signature SignedInfo 结构上计算的。它们不能从 .NET 公共语言运行库对象中的数据计算。

计算/验证 XML 签名值的一种方法是使用应用程序代码把对象的序列化处理为 XML,并确保计算 XML 签名所需的 XML 结构可以使用。该方法被拒绝,因为它不能利用 ASP.NET 提供的 XML 序列化和 SOAP 消息生成功能,大大增加了应用程序代码的复杂性,并使应用程序的维护更加困难。

当然,我们承认可以把 XML 签名生成和验证的问题从 XKMS 应用程序的其他部分分离开来。因为 XML 签名操作需要 SOAP 消息内容,所以可以把它们实现为 ASP.NET SOAP 扩展(参见 System.Web.Services.Protocols.SoapExtension)。我们还承认签名操作对于在 Register 请求 SOAP 消息中通信的数据不可知。因此,可以使用独立于任何特定客户端或服务实现的方法处理它们。示例代码中提供了一个 XKMS 签名 SOAP 扩展(参见 XSigner 目录),用于 XML 签名的 XKMS SOAP 扩展部分对此进行了描述。

XKMS 服务实现
返回到 wsdl 实用工具生成的原型代码 (XSchema\XKMSProto.cs),可以找到 XKMS 服务公共公开方法的抽象实现。这些实现用控制 Web 服务名称、XML 命名空间和参数序列化的属性作了批注。

从该实现创建一个服务实现非常简单。我们只需把 KeyService 类定义和相关联的属性复制到一个 ASMX 文件并添加必需的 Web 服务声明:

<%@ WebService Language="C#" class="XKMS.KeyService" %>

对于该示例代码,我们还把 KeyService 类放置在命名空间 'XKMS' 中。这不是必需的,但有助于清楚 'KeyService' 的用途。

下一步,从 KeyService 类及其方法中移除 'abstract' 关键字。最后,把应用程序代码添加到每个方法。对于本示例,使用的是代码隐藏实现。因此,ASMX 文件中的方法只是把输入参数传递给代码隐藏 XKMSService 类中的相关方法。该方法把描述 XKMS 服务公共接口的代码和示例实现特有的应用程序逻辑清楚地分离。

实现中的 KeyService 类上有几个 XmlIncludeAttributes。如果查看这些属性包含的 'types',将会发现这些类型已经显式用于公共 WebMethod 的参数值。您也许奇怪为什么会存在这些类型。答案是它们不是必需的,可以删除。这些是由 wsdl 实用工具生成的,因为生成原型代码时跟踪数组类型的用法受到限制。

本部分讨论的服务实现将有一个副作用,用户应该注意。如果允许 ASP.NET 从服务自动生成 WSDL 描述,则该描述将不符合 XKMS 规范 WSDL。具体地说,ProofOfPossession 和 KeyBindingAuth 类型定义将以字符串类型出现,而不是带有签名类型子元素的复杂类型。这是我们决定把 XKMS 签名操作实现为 SOAP 扩展的结果。ASP.NET 不知道 XKMS 签名 SOAP 扩展实现的转换,因此不能在它生成的 WSDL 中反映此转换。但是,这不影响服务实现符合 XKMS 规范。

要处理该问题,服务可以取消生成的 WSDL并显式包含来自 XKMS 规范的 WSDL。ASP.NET 生成的 WSDL 由文档 'protocol' 控制。可以通过包含如下代码在配置文件中阻止它:

<webServices>
         <protocols>
          <remove name="Documentation"/>
      </protcols>
   </WebServices>.

但是,也许您希望生成的 WSDL 能为使用 .NET 框架的客户端应用程序开发人员提供帮助,正如构建 XKMS 客户端部分所讨论的一样。

返回页首
用于 XML 签名的 XKMS SOAP 扩展
正如前面部分讨论的一样,示例实现依赖于 ASP.NET SOAP 扩展处理 XML 签名生成和验证。这是在 Register 请求消息内正确处理可选 ProofOfPossession 和 KeyBindingAuth 元素所必需的。通过为该功能使用 SOAP 扩展,您可以在利用这些复杂操作的公共实现的同时构建 XKMS 客户端和服务实现。

XKMS 签名 SOAP 扩展在 XSigner 目录中提供。此代码用于构建 XKMSSigner 程序集,该程序集计算并验证符合 XKMS 规范的 XML 签名。XKMSSigner 不包含应用程序决策逻辑,并对 SOAP 消息中编码的数据不可知。它被设计为只处理 Register 请求消息而不更改其他可能传递给它的 SOAP 消息。

要了解 XKMSSigner 实现,读者应该熟悉从 System.Web.Services.Protocols.SOAPExtension 和 System.Web.Services.Protocols.SOAPExtensionAttribute 中派生的类的创建和使用。这些实现类中完成的处理很简单。它们提供了一种方法用于表示何时应调用 XKMSSigner,并为按需要调用签名和验证例程提供支持。

XKMSSigner 实现中更有趣的部分是签名和验证方法(参见 XSigner\XKMSSigner.cs)。此代码的大部分用来处理检查 SOAP 消息结构并查找签名操作所需要的节点。这些检查确保输入 XML 是有效的 SOAP 并且正文包含 Register 请求消息。它还查找 Prototype 元素、验证时 Prototype 元素内的公钥值以及 ProofOfPossession 和/或 KeyBindingAuth 元素。然后它为签名操作创建适当的文档上下文以确保互操作性。实际的签名计算/验证代码是整个实现中相对较小的部分。

与 XKMS 客户端一起使用时,XKMSSigner 需要几条信息以执行它的工作。这些包括:

• 所需的签名。这取决于有或没有 ProofOfPossession 和 KeyBindingAuth 元素(作为 AuthUserInfo 或 AuthServerInfo 的子元素)。在示例代码中,XKMS 客户端把这些字段设置为值 "Create" 以表示需要相应的签名,尽管可以使用任何非空字符串值。如果 Register 请求 SOAP 消息中有这些元素,会生成适当的签名以替换该元素所有现有的子元素。

• 客户端私有签名密钥。通过把密钥放置在线程上下文的命名数据槽中,XKMSSigner 可以使用该密钥。该数据槽名为 "POPKeyValue"。该方法确实使任何拥有线程上下文访问权限的代码可以使用私有密钥值,但在某些环境中也显示出无法接受的安全风险。还有其他更安全的方法可以传递该信息。但是,这些方法的讨论已超出本文的范围。

• 客户端和服务之间预先安排的 KeyBindingAuth 'secret' 词组。Secret 用于计算 HMAC 密钥值,该密钥值在 HMAC-SHA1 XML 签名中使用。通过把 secret 放置在名为 "BindingSecret" 的线程上下文数据槽中,可以使用它。同样,取决于用户环境,您或许希望使用更安全的方法传递该值。

对于该服务实现,XKMSSigner 只需要在生成 KeyBindingAuth HMAC 密钥中使用的 'secret' 词组。用于验证 ProofOfPossession 签名的公钥总是包含在 Prototype 元素的 KeyInfo 子元素中。

假定 KeyBindingAuth secret 词组可能对于每个客户端都不一样,并且可能对于一个客户端作出的每次密钥注册尝试也不一样。因此,直到 Register 请求消息到达之后才能决定要使用的正确 secret。XKMSSigner 实现了一个方法 XKMSConfig.ResolveClientSecret (string keyname) 以查找正确的 secret。从签名验证方法中调用它。假定 keyname 参数可能是 KeyID 或 KeyName 值,无论服务决定使用的是哪个,作为已发送的 secret 列表中的索引都非常有用。ResolveClientSecret 方法的示例代码需要修改以使用 XKMS 服务为管理 secret 列表而创建的任何机制。当然,这假设服务支持 KeyBindingAuth 机制。

因为 XKMSSigner 代码在 XKMS 服务代码运行之前验证签名,所以我们需要一种方法把那些验证操作的结果传递给服务。示例使用一种简单的方法。验证签名后,从 ProofOfPossession 或 KeyBindingAuth 元素中移除相应的签名子元素。然后,插入文本值 "Success" 或 "Failure" 以通知验证操作的结果。

为了使用户能查看 XKMSSigner 输入和输出 SOAP 消息,还内置了简单的记录工具。这由配置文件的 appSettings 节中一个值的存在控制。例如,项:

<add key="XKMSLog" value="c:\XKMS.Log"/>

告诉XKMSSigner 把信息记录到文件 C:\XKMS.Log 中。

合并其他转换
您会注意到 XKMSSigner.cs 示例代码大部分与验证和转换 SOAP Register 请求消息有关。假设我们已经转换了消息 XML,您可以很容易地把其他消息转换操作合并到该扩展中。示例代码中包含一个简单的示例,它更改空值的编码方式。

SOAP 规范允许您以两种基本方法表示空值。首先,可以省略相应的 XML 元素。第二,可以包含一个 XML 元素,该元素显式带有包含值 "true" 的 'nil' 属性。考虑 Register 请求消息的 Prototype 子元素。如果 Prototype 结构的 KeyID 值为空,那么您可以发送(显示部分 SOAP 消息):

<soap:Body>
   <Register xmlns="http://www.xkms.org/schema/xkms-2001-01-20">
      <Prototype Id="KB01">
         <Status>Valid</Status>
         ...
      </Prototype>
   </Register>
<soap:Body>

<soap:Body>
   <Register xmlns="http://www.xkms.org/schema/xkms-2001-01-20">
      <Prototype Id="KB01">
         <Status>Valid</Status>
         <KeyID xsl:nil='true'/>
         ...
      </Prototype>
   </Register>
<soap:Body>

假设一些 XKMS 服务在使用 nil 属性编码反序列化消息时有问题(它们不会有问题,但这是个让我们说明该技术的简单示例)。为了适应这种情况,XKMSSigner 能移除带有 nil 属性值为 true 的元素。您可以在配置文件的 appSettings 节中设置一个值来进行控制 (<add key="XKMSRemoveNils" value="true"/>)。如果请求,该操作在进行签名计算之前完成,因为在签名生成之后该消息的任何转换很可能破坏签名。

返回页首
构建 XKMS 客户端
创建 XKMS 服务代理
有两种可行的方法用于为客户端应用程序创建 XKMS 服务代理。每种方法都允许您生成完全符合 XKMS 规范的客户端。

首先,您可以对 XKMS 规范 WSDL 运行 wsdl 实用工具,就像生成服务原型代码一样。就像服务的描述一样,得到的代理代码也需要手动编辑才能使用。

第二,您可以允许 ASP.NET 为我们的示例服务实现生成 WSDL 描述并对它运行 wsdl 实用工具。使用该方法,不需要手动编辑代理代码,因为服务 WSDL 已经有了必要的修改。该方法用于示例代码。

XKMS 规范 WSDL 和示例服务实现 WSDL 之间的差异在 XKMS 服务实现部分中讨论。很明显,作为 XKMS 客户端开发人员,您应该只依赖于可用的 XKMS 规范 WSDL。但是使用示例服务 WSDL 可以简化您的工作。

XKMS 客户端实现
有了用于 XKMS 服务的代理,构建客户端应用程序很简单。代理包含服务公开的方法以及使用那些方法所需要的所有类型。

示例客户端代码(参见 XClient/Client.cs)是一个简单的控制台应用程序,它能:

• 使用服务注册 RSA 公钥值

• 使用服务注册 RSA 公钥值

• 根据 KeyName 值查找 RSA 公钥

• 根据 KeyID 或 KeyName 值验证 RSA 公钥

通过应用程序交互式地收集任何所需的输入,用户可以执行一系列这些操作。

示例中没有实现其他 XKMS 定义的功能。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
46.875ms