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

深入了解 SecureConversation:第一部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (7投票s)

2006 年 11 月 17 日

CPOL

15分钟阅读

viewsIcon

76553

downloadIcon

1888

深入探究增强型 Web 服务的安全协议。

引言

当您使用联合绑定时,WCF 会向身份提供程序服务器请求一个身份验证令牌。此身份提供程序实现一个 STS(安全令牌服务),该服务生成一个 XML 消息来传输安全令牌。应用程序与 STS 之间的此消息交换,在其最简单的形式中,涉及由请求应用程序生成的 RST 消息(请求安全令牌)以及由 STS 作为对 RST 的响应而生成的 RSTR 消息(请求安全令牌响应)。

基本上,STS 实现 WS-Trust 合约的某些操作,最少的是 Issue 操作,它在收到 RST 后生成 RSTR 消息。WCF 框架不实现这些机制,但提供了编写这些机制所必需的实现。事实上,RST 的生成是在 Cardspace 中完成的,但 WCF 中没有提供特定类来支持它。至于 RSTR,Microsoft 提供了一些示例实现,但在 WCF API 中没有类支持。当您为 WS-Federation 设置 WCF 时,您会激活 WS-* 机制来保护消息的传输。WCF 中的安全是一个非常严肃的问题,我们将看到 Microsoft 如何应用 WS-* 规范。

当使用 WSHttp* 绑定时,消息安全会被激活,这意味着 WCF 将使用 WS-* 标准来保护信息传输,在本例中是 RST 和 RSTR。这涉及到 WS-SecureConversation、WS-Security 等规范,后者使用 XML 加密 (XMLEnc) 和 XML 签名 (XML Dsig)。这些规范可以在 OASIS 网站上找到。我们将要分析的案例使用了 SAML 令牌,RST/RSTR 消息是由 Microsoft 提供的 STS 示例生成的,该示例运行在 WCF 的 July CTP 上。

我本人正在编写一个必须与 Microsoft 框架互操作的全功能 STS 服务器,并且考虑到我在 WCF 框架中集成我的 STS 时遇到的问题,我寻找了一个可以验证我响应 WCF RST 所创建的 RSTR 的工具,但我找不到任何。于是我决定自己写一个!我首先基于与 WCF May CTP 的 Infocard 示例交换的消息编写了一个 RSTR 验证器。在 May 版本和 July 版本之间,在 WS-SecureConversation 的使用方面进行了许多配置更改,以使其尽可能安全。

消息分析要求您拥有用于消息加密和签名的所有证书的私钥。STS 的证书用于建立请求应用程序和 STS 之间的安全会话。

背景

本文不包含任何代码,但是,它假设您对 XML 有良好的了解,并对基本加密技术(如使用对称和非对称算法进行加密和签名)有一定的理解。我只是附加了一个小工具,它可以帮助理解本文所使用的加密基础知识。

Cardspace 的配置

Cardspace 使用托管卡进行配置,该卡使用用户名-密码身份验证,并指向我们的 MEX 地址。Cardspace 要求 MEX 由 HTTPS 服务器发送。我们配置了我们的 MEX 指向托管我的 STS 的服务器,并向 Cardspace 提供 STS 证书。此配置避免了使用 SCT 协议交换证书。当然,任何符合 Cardspace 的 STS 都可以像 Microsoft 发布的一样进行。

WCF 不允许获取我们将要分析的完整请求和响应消息。为了获取这些消息,我在一台 PC 上使用了 Cardspace,STS 托管在另一台机器上。使用 Ethereal 等工具,您可以监听 HTTP 通道并提取消息。如果您熟悉 WCF,您会知道您可以跟踪 XML 消息,但是,Microsoft 会从消息中删除所有二进制数据,例如计算派生密钥所需的 Nonce。

分析 WS-SecureConversation

WS-SecureConversation 是一个相当复杂的规范,并且能够适应许多安全挑战。WCF 目前实现了其大部分可能的配置。在本文中,我将分析通过运行我的 STS 实现作为 Cardspace 的身份提供程序而获得的几个 RST/RSTR。

安全会话用于保护 XML 消息中 RST/RSTR 交换的传输。它使用两个安全概念:隐私和完整性。隐私通过使用 XML 加密来加密消息中要传输的数据来确保,而完整性通过使用 XML 签名来签名消息的某些部分来确保。此 XML 安全要求一个带有私钥的证书位于 STS 中。客户端应用程序只需知道此证书的公钥。我们将首先分析消息正文的加密,然后查看签名如何使用,最后查看 STS 如何验证客户端。所有这些都有助于理解 WCF 的工作原理,以及如何编写一个与 WCF 在会话级别互操作的身份提供程序。然后,当达到此互操作级别时,就可以构建一个完全可互操作的身份提供程序。

WSstarUtility 应用程序

为了让您测试本文中开发的各种概念,我编写了一个简单的实用程序应用程序,您可以使用它来解密我们将要分析的 SOAP 消息的各种元素。此应用程序设计用于使用 Base 64 数据,这是通过 XML 传输二进制数据所使用的编码类型。这只是我开发的一个简单工具,可以让本文更具交互性。请注意,用户界面非常基础,没有任何输入检查。但是,如果输入错误数据,会处理异常。

对于 RSA 解密,它使用协议用来创建消息的证书,因此在使用应用程序之前,请不要忘记安装证书。

在使用应用程序之前,您必须阅读消息分析...

以下是此加密应用程序的一些屏幕截图。

此屏幕是应用程序的主屏幕。每个按钮都会打开一个对话框,用于执行分析 SOAP 消息所使用的特定加密功能。

  • 使用 RSA 解密:使用 RSA 公钥算法解密数据(需要证书),用于 EncryptedKey
  • 使用 AES 解密:使用对称 AES 算法解密数据,用于 EncryptedData
  • 计算 SHA:计算某些数据的 SHA1,用于签名摘要
  • 计算 HMACSHA1:计算带有 SHA1 摘要的 HMAC,用于签名
  • 计算 PSHA1:使用 PSHA1 计算派生密钥

此屏幕显示了使用 SOAP 消息数据的母密钥解密。所有数据都以 Base64 输入,因为这是您从 SOAP 消息中获得的内容。输出可以是二进制或 Base64。

上面的屏幕显示了派生密钥的计算。与前一个一样,输入密钥应以 Base64 格式输入。

此屏幕说明了消息正文的解密。使用的密钥是之前使用 PSHA1 函数计算的派生密钥。结果以 ASCII 显示,并且是一个 RequestSecurity 元素。

第一部分:包含 RST(RequestSecurityToken)的请求 SOAP 消息

SOAP 消息结构

SOAP 消息包含两个元素:HeaderBodyHeader 包含消息的信息,例如安全数据,而 Body 包含消息本身的内容。

Header 通常包含 ActionMessageIDTo 等元素,以及最重要的 Security 元素。

Security 元素包含解密消息的加密部分和验证签名所需的所有信息。消息、Header 和 Body 中包含的加密元素列表列在 Security/ReferenceList 元素中。此元素包含 DataReference 元素,这些元素允许通过其 Id 属性查找不同的 EncrytpedData。此元素的外观如下:

<e:ReferenceList xmlns:e="http://www.w3.org/2001/04/xmlenc#">
   <e:DataReference URI="#_3" />
   <e:DataReference URI="#_8" />
</e:ReferenceList>

解密 Body 元素的内容

SOAP 消息的 Body 使用会话密钥加密。SOAP 消息中的典型 Body 如下所示:

列表 1

<s:Body u:Id="_2">
  <e:EncryptedData Id="_3" 
     Type="http://www.w3.org/2001/04/xmlenc#Content" 
     xmlns:e="http://www.w3.org/2001/04/xmlenc#">
    <e:EncryptionMethod 
        Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <o:SecurityTokenReference 
         xmlns:o="http://docs.oasis-open.org/wss/2004/01/
                  oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <o:Reference URI="#_1"/>
      </o:SecurityTokenReference>
    </KeyInfo>
    <e:CipherData>
      <e:CipherValue>DjVrX8LpF3I6T5jFXG…WjG0c+taQ==</e:CipherValue>
    </e:CipherData>
  </e:EncryptedData>
</s:Body>

Body 的内容被加密在 EncryptedData 元素中。EncryptedData 元素的完整描述可以在 XML 加密规范 此处找到。

此加密数据的类型是 ContentEncryptionMethod 元素中的 Algorithm 属性给出了使用的加密算法。在这种情况下,用于加密数据的对称算法是 AES 256。

<e:EncryptionMethod 
   Algorithm=http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>

KeyInfo 元素允许解析用于使用 AES 算法加密数据的密钥。此 KeyInfo 使用 SecurityTokenReference,它指向另一个 Id 等于 _1 的元素。

<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <o:SecurityTokenReference 
       xmlns:o="http://docs.oasis-open.org/wss/2004/01/
                oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <o:Reference URI="#_1" />
  </o:SecurityTokenReference>
</KeyInfo>

此引用是 SOAP 消息的 Header 元素下可以找到的一个元素。在本例中,此引用是 DerivedKeyToken,它包含派生用于加密 Body 数据密钥的必要数据。此元素如列表 2 所示。

列表 2

<c:DerivedKeyToken u:Id="_1" 
    xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
  <o:SecurityTokenReference>
    <o:Reference 
          ValueType="http://docs.oasis-open.org/wss/
                     oasis-wss-soap-message-security-1.1#EncryptedKey" 
       URI="#uuid-4c692bc8-381a-4983-be4b-ef7dbfd00935-17"/>
  </o:SecurityTokenReference>
  <c:Offset>0</c:Offset>
  <c:Length>16</c:Length>
  <c:Nonce>KQBSz90ZzToGt9WV3lOIeA==</c:Nonce>
</c:DerivedKeyToken>

DerivedKeyToken 元素包含两个用于计算派生密钥的有用数据。一个是另一个 Reference,这次指向一个 EncryptedKey 元素,其 Id 为 "uuid-4c692bc8-381a-4983-be4b-ef7dbfd00935-17",另一个是 Base64 编码的 nonce 值,用于计算 EncryptedKey 元素中密钥的派生密钥。

密钥派生在 WS-SecureConversation 规范中有描述。使用的密钥派生算法定义为 PSHA1。此函数是:

  key = PSHA1(secret, label + seed)

其中

  • secret 是母密钥
  • seed 是 nonce
  • label 是可以给出的标签,或者使用默认值。

当标签未随 nonce 一起发送时,必须使用定义为 "WS-SecureConversation" 的默认值。但是,Microsoft 在其实现中使用的默认标签不同,其值为 "WS-SecureConversationWS-SecureConversation"。

要从 PSHA1 的结果中提取派生密钥,需要长度和偏移量。在本例中,派生密钥应该是 16 字节,因为它与 AES 128 算法一起使用。由于 DerivedKeyToken 中没有给出 Offset 值,因此应将其设置为 0。

加密密钥由下面列表中的元素描述。此密钥称为会话密钥,也用于计算其他派生密钥。它还用于加密响应消息的 Security 元素中的 Body,以及可选的 Signature 元素。此密钥通常使用 RSA 算法加密,该算法使用 STS 证书的公钥进行加密。这假设构建 RST 消息的客户端应用程序拥有 STS 的证书,或者在证书协商期间获得了它。

列表 3

<e:EncryptedKey Id="uuid-4c692bc8-381a-4983-be4b-ef7dbfd00935-17" 
   xmlns:e="http://www.w3.org/2001/04/xmlenc#">
  <e:EncryptionMethod 
        Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" 
     xmlns="http://www.w3.org/2000/09/xmldsig#" />
  </e:EncryptionMethod>
  <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
    <o:SecurityTokenReference>
    <o:KeyIdentifier 
       ValueType="http://docs.oasis-open.org/wss/oasis-wss-
                  soap-message-security-1.1#ThumbprintSHA1" 
       EncodingType="http://docs.oasis-open.org/wss/2004/01/
                     oasis-200401-wss-soap-message-
                     security-1.0#Base64Binary">
       WELxHZqkA1Xvh7A9ebgWRrW2kf0=</o:KeyIdentifier>
    </o:SecurityTokenReference>
  </KeyInfo>
  <e:CipherData>
    <e:CipherValue>XsApZde4j3w35HGt…YVHkbY0MFZIg=</e:CipherValue>
  </e:CipherData>
</e:EncryptedKey>

EncryptedKey 元素与我们之前看到的 EncryptedData 元素非常相似。它包含一个 EncryptionMethod 元素,该元素给出用于加密密钥的加密算法,以及一个 KeyInfo 来获取此算法中使用的加密密钥。

用于加密密钥的算法是 RSA-OAEP-MGF1P 和 SHA1 摘要。这是一种非对称算法,需要公钥进行加密,需要私钥进行解密。这意味着 KeyInfo 应描述一种方式,以便 STS 找到与用于加密的公钥相对应的私钥。在这种情况下,KeyInfo 提供了一个指向用于加密密钥的证书的引用。有了此引用,STS 应该能够从其证书存储中提取私钥。KeyInfo 的形式如下:

<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
  <o:SecurityTokenReference>
    <o:KeyIdentifier 
       ValueType="http://docs.oasis-open.org/wss/oasis-
                  wss-soap-message-security-1.1#ThumbprintSHA1"
       EncodingType="http://docs.oasis-open.org/wss/2004/01/
                     oasis-200401-wss-soap-message-
                     security-1.0#Base64Binary">
           WELxHZqkA1Xvh7A9ebgWRrW2kf0=
    </o:KeyIdentifier>
  </o:SecurityTokenReference>
</KeyInfo>

KeyInfoSecurityTokenReference 包含一个 KeyIdentifier,该标识符允许基于用于加密的证书的指纹查找私钥。此密钥标识符值是此指纹的 base64 编码。此字符串可以转换为其二进制值,并用于从 Windows 的证书存储等中获取私钥,以解密加密的密钥。

解密 Body 的摘要

  • 使用指纹,获取 STS 证书的私钥。
  • 使用 RSA-OAEP-MGF1P 解密会话密钥。
  • 使用以下参数通过 PSHA1 算法获取派生密钥
    • Nonce
    • Label=WS-SecureConversationWS-SecureConversation
    • 会话密钥
  • 使用派生密钥(之前通过 PSHA1 函数计算的)使用 AES128 算法解密 Body 的加密内容。解密的元素应为 RequestSecurityToken 类型。

解密 Header 的 Security 元素中的加密元素

通常,HeaderSecurity 元素包含一个或多个 EncryptedData 元素。这些元素类型为 Element。每个元素都包含一个可以是 Signature 或身份验证令牌的元素,通常是 UsernameToken。当使用证书身份验证时,该元素不会被加密,而是 BinarySecurityToken 类型。

这些元素的加密方法与我们之前解密的 Body 内容的加密方法完全相同。只需应用相同的模式来解密它们。

应注意的是,签名加密不是强制性的,它们可以以未加密的形式传输到 Security 元素中。这取决于 STS 请求的策略。

在我使用的示例中,Signature 未加密,并且有一个类型为 'element' 的 EncryptedData 元素需要解密。用于解密 Body 的派生密钥也用于此。

数据解密后,我们会得到一个 UsernameToken,其中包含服务器用于验证请求方应用程序的 UsernamePassword。列表 4 显示了解密结果。

列表 4

<o:UsernameToken u:Id="uuid-5fbebc00-4101-4606-aa0b-b1aa2a04d6f9-34" 
   xmlns:o="http://docs.oasis-open.org/wss/2004/01/
            oasis-200401-wss-wssecurity-secext-1.0.xsd"
   xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-
            200401-wss-wssecurity-utility-1.0.xsd">
  <o:Username>orouit</o:Username>
  <o:Password 
    o:Type="http://docs.oasis-open.org/wss/2004/01/oasis-
            200401-wss-username-token-profile-1.0#PasswordText">
        gemalto
    </o:Password>
</o:UsernameToken>

验证签名

Signature 元素用于完整性验证。在我们研究的案例中,使用的身份验证方法是用户名密码。实际上,对于此身份验证方法,只有一个 Signature 元素;如果我们使用证书身份验证,则会有另一个使用 RSA 签名的 Signature 元素,并带有请求者证书。

但是,让我们来处理我们的案例,这个案例实际上要简单一些。放在 Security 中的 Signature 元素用于验证消息的多个元素的完整性。这些元素的列表可以在 STS 在第一次交换中发送的 MEX 信息中配置。列表 5 显示了此示例消息的 Signature 的一个片段。为了简短,我只删除了指向要签名的元素的一些 Reference

重要的是要注意 Signature 元素可以被加密在 Security 元素中。在这种情况下,签名只是以明文形式放置。签名加密是请求方应用程序配置的问题。会话层应该能够适应任何配置。

列表 5

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    <CanonicalizationMethod 
       Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
    <SignatureMethod 
      Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
    <Reference URI="#_2">
      <Transforms>
        <Transform 
          Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      </Transforms>
      <DigestMethod 
        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>Ymirle7eo8IRR4QdSre7zreo0Ho=</DigestValue>
    </Reference>
    …
    <Reference URI="#uuid-4c692bc8-381a-4983-be4b-ef7dbfd00935-18">
      <Transforms>
        <Transform 
          Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      </Transforms>
      <DigestMethod 
        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>DBRKoGWw1qKmROVPT88IrjBC0QM=</DigestValue>
    </Reference>
    <Reference 
        URI="#uuid-5fbebc00-4101-4606-aa0b-b1aa2a04d6f9-34">
      <Transforms>
        <Transform 
          Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      </Transforms>
      <DigestMethod 
        Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>ejz93PC9wJJpvxFW4hcj5pZvFXo=</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>vdfeD+W/p++f2LBCAwNU3oVH0/0=</SignatureValue>
  <KeyInfo>
    <o:SecurityTokenReference>
      <o:Reference URI="#_0" />
    </o:SecurityTokenReference>
  </KeyInfo>
</Signature>

参与签名过程的元素在 SignedInfo 元素中进行描述。首先,我们找到规范化方法,它引用在签名之前序列化 SignedInfo 元素的方式,然后是用于签名序列化数据的签名方法。在这种情况下,使用 C14N 规范化,签名是使用 KeyInfo 元素中引用的签名密钥对规范化结果进行的 HMAC-SHA1。

然后,我们找到几个 Reference 元素,它们包含指向消息中某个元素的 URI、要对此元素应用以获得摘要的转换以及摘要数据本身。同样,使用 CN14 规范化方法,通过对规范化数据执行 SHA1 来获得摘要。

用于签名 SignedInfo 元素的密钥的引用由 KeyInfo 元素给出。KeyInfo 中的引用指向以下 DerivedKey 元素:

<c:DerivedKeyToken u:Id="_0" 
      xmlns:c="http://schemas.xmlsoap.org/ws/2005/02/sc">
  <o:SecurityTokenReference>
    <o:Reference 
       ValueType="http://docs.oasis-open.org/wss/oasis-
                  wss-soap-message-security-1.1#EncryptedKey"    
       URI="#uuid-4c692bc8-381a-4983-be4b-ef7dbfd00935-17" />
  </o:SecurityTokenReference>
  <c:Offset>0</c:Offset>
  <c:Length>16</c:Length>
  <c:Nonce>SQHzMAYbmyZQOQ2jsyEbcg==</c:Nonce>
</c:DerivedKeyToken>

DerivedKeyToken 元素与用于获取解密 BodyUsernameToken 元素的密钥的元素严格相似,并且它引用相同的会话密钥。

为了验证签名值,STS 必须计算 SignedInfo 中每个引用元素的摘要值,并与原始 SignedInfo 中的值进行检查。如果所有摘要都匹配,则必须使用 C14N 规范化方法对 SignedInfo 元素进行序列化,并检查 HASHMAC-SHA1 是否与原始值匹配。如果匹配,则签名完全验证。

关注点

当您激活 WCF 的安全功能时,这就是客户端和服务器应用程序之间交换的所有消息在协议级别上发生的情况。到目前为止,我们只看到了请求消息。响应消息将在本文的第二部分讨论。

使用 WCF 框架编写您的服务可以为您省去所有协议的负担。但是,我认为对于那些更深入研究协议级别的人来说,详细解释此级别的整个交换过程会很有趣。互联网安全已成为 Microsoft 等大公司优先考虑的事项。我希望本文能帮助那些希望了解 Web 服务安全幕后情况的人。

响应消息的分析将在本文的 第二部分 进行讨论。

© . All rights reserved.