XMPP协议TLS、SASL、资源绑定和服务器回调详细流程

来源:互联网 发布:人工智能机器人在哪做 编辑:程序博客网 时间:2024/06/06 18:26

TLS 的使用

概览

XMPP包含的一个保证流安全的方法来防止篡改和偷听.这个传输层安全协议[TLS]的频道加密方法, 模拟了类似的其他"STARTTLS"(见RFC 2595 [USINGTLS])的扩展,如 IMAP [IMAP], POP3 [POP3], and ACAP [ACAP]."STARTTLS"的扩展名字空间是'urn:ietf:params:xml:ns:xmpp-tls'.
一个给定域的管理员可以(MAY)要求客户端和服务器通信以及服务器之间通信时使用TLS,或者两者都要求。客户端应该(SHOULD)在尝试完成 SASL (第六章)握手之前使用 TLS,服务器应该(SHOULD)在两个域之间使用 TLS 以保证服务器间通信的安全。
以下是使用规则:
  1. 一个遵守本协议的初始化实体必须(MUST)在初始化流的头信息中包含一个'version'属性并把值设为“1.0”。
  2. 如果TLS握手发生在两个服务器之间,除非服务器声称的DNS主机名已经被解析(见第十四章第四节 Server-to-Server Communications),通信不能(MUST NOT)继续进行。
  3. 当一个遵守本协议的接收实体接收了一个初始化流(它的头信息中包含一个'version'属性并且值设为“1.0”),在发送应答流的的头信息(其中包含版本标记)之后,它必须发送(MUST)<starttls/>元素(名字空间为 'urn:ietf:params:xml:ns:xmpp-tls')以及其他它支持的流特性 。
  4. 如果初始化实体选择使用TLS,TLS握手必须在SASL握手之前完成;这个顺序用于帮助保护SASL握手时发送的认证信息的安全,同时可以在必要的时候在TLS握手之前为SASL外部机制提供证书。
  5. TLS握手期间,一个实体不能(MUST NOT)在流的根元素中发送任何空格符号作为元素的分隔符(在下面的TLS示例中的任何空格符都仅仅是为了便于阅读);这个禁令用来帮助确保安全层字节精度。
  6. 接收实体必须(MUST)在发送<proceed/> 元素的关闭符号">" 之后立刻开始TLS协商。初始化实体必须(MUST)在从接收实体接收到<proceed/> 元素的关闭符号">" 之后立刻开始TLS协商。
  7. 初始化实体必须(MUST)验证接收实体出示的证书;关于证书验证流程参见Certificate Validation ( 第十四章第二节)。
  8. 证书必须(MUST)检查初始化实体(比如一个用户)提供的主机名;而不是通过DNS系统解析出来的主机名;例如,如果用户指定一个主机名"example.com"而一个DNS SRV [SRV]查询返回"im.example.com",证书必须(MUST)检查"example.com".如果任何种类的XMPP实体(例如客户端或服务器)的JID出现在一个证书里,它必须(MUST)表现为一个别名实体里面的UTF8字符串,存在于subjectAltName之中。如何使用 [ASN.1] 对象标识符 "id-on-xmppAddr" 定义在本文的第五章第一节第一小节。
  9. 如果 TLS 握手成功了,接收实体必须(MUST) 丢弃TLS 生效之前从初始化实体得到的任何不可靠的信息.
  10. 如果 TLS 握手成功了,初始化实体必须(MUST) 丢弃TLS 生效之前从接收实体得到的任何不可靠的信息.
  11. 如果 TLS 握手成功了,接收实体不能(MUST NOT)在流重新开始的时候通过提供其他的流特性来向初始化实体提供 STARTTLS 扩展.
  12. 如果 TLS 握手成功了,初始化实体必须(MUST)继续进行SASL握手。
  13. 如果 TLS 握手失败了,接收实体必须(MUST)终止XML流和相应的TCP连接。
  14. 关于必须(MUST)支持的机制,参照 Mandatory-to-Implement Technologies (第十四章第七节) 。

用于 XMPP 地址的 ASN.1 对象标识符

上文提到的[ASN.1] 对象标识符 "id-on-xmppAddr"定义如下:
id-pkix OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
dod(6) internet(1) security(5) mechanisms(5) pkix(7) }
id-on OBJECT IDENTIFIER ::= { id-pkix 8 } -- other name forms
id-on-xmppAddr OBJECT IDENTIFIER ::= { id-on 5 }
XmppAddr ::= UTF8String
对象标识符也可以(MAY)使用点分隔的格式,如 "1.3.6.1.5.5.7.8.5".

叙述

当一个初始化实体用TLS保护一个和接收实体之间的流,其步骤如下:
  1. 初始化实体打开一个TCP连接,发送一个打开的XML流头信息(其'version'属性设置为"1.0")给接收实体以初始化一个流。
  2. 接收实体打开一个TCP连接,发送一个XML流头信息(其'version'属性设置为"1.0")给初始化实体作为应答。
  3. 接收实体向初始化实体提议STARTTLS范围(包括其他支持的流特性),如果TLS对于和接收实体交互是必需的,它应该(SHOULD)在<starttls/>元素中包含子元素<required/>.
  4. 初始化实体发出STARTTLS命令(例如, 一个符合'urn:ietf:params:xml:ns:xmpp-tls'名字空间的 <starttls/> 元素) 以通知接收实体它希望开始一个TLS握手来保护流。
  5. 接收实体必须(MUST)以'urn:ietf:params:xml:ns:xmpp-tls'名字空间中的<proceed/>元素或<failure/>元素应答。如果失败,接收实体必须(MUST)终止XML流和相应的TCP连接。如果继续进行,接收实体必须(MUST)尝试通过TCP连接完成TLS握手并且在TLS握手完成之前不能(MUST NOT)发送任何其他XML数据。
  6. 初始化实体和接收实体尝试完成TLS握手。(要符合[TLS]规范)
  7. 如果 TLS 握手不成功, 接收实体必须(MUST)终止 TCP 连接. 如果 TLS 握手成功, 初始化实体必须(MUST)发送给接收实体一个打开的XML流头信息来初始化一个新的流(先发送一个关闭标签</stream>是不必要的,因为接收实体和初始化实体必须(MUST)确保原来的流在TLS握手成功之后被关闭) 。
  8. 在从初始化实体收到新的流头信息之后,接收实体必须(MUST)发送一个新的XML流头信息给初始化实体作为应答,其中应包含可用的特性但不包含STATRTTLS特性。

客户端-服务器 示例

以下例子展示一个客户端使用STARTTLS保护数据流 (注意: 以下步骤举例说明协议中的失败案例;在这个例子中它们并不详尽并且也不是必须被数据传输触发的).
步骤 1: 客户端初始化流给服务器:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 2: 服务器发送一个流标签给客户端作为应答:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_123' from='example.com' version='1.0'>
步骤 3: 服务器发送 STARTTLS 范围给客户端(包括验证机制和任何其他流特性):
   <stream:features>     <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>       <required/>     </starttls>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>PLAIN</mechanism>     </mechanisms>   </stream:features>
步骤 4: 客户端发送 STARTTLS 命令给服务器:
   <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
步骤 5: 服务器通知客户端可以继续进行:
   <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
步骤 5 (或者): 服务器通知客户端 TLS 握手失败并关闭流和TCP连接:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>   </stream:stream>
步骤 6: 客户端和服务器尝试通过已有的TCP连接完成 TLS 握手.
步骤 7: 如果 TLS 握手成功, 客户端初始化一个新的流给服务器:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 7 (或者): 如果 TLS 握手不成功, 服务器关闭 TCP 连接.
步骤 8: 服务器发送一个流头信息应答客户端,其中包括任何可用的流特性:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='c2s_234' version='1.0'>   <stream:features>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>PLAIN</mechanism>       <mechanism>EXTERNAL</mechanism>     </mechanisms>   </stream:features>
步骤 9: 客户端继续 SASL 握手 (第六章).

服务器-服务器示例

以下例子展示两个服务器之间使用STARTTLS保护数据流 (注意: 以下步骤举例说明协议中的失败案例;在这个例子中它们并不详尽并且也不是必须被数据传输触发的).
步骤 1: Server1 初始化流给 Server2:
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 2: Server2 发送一个流标签给 Server1 作为应答:
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='s2s_123' version='1.0'>
步骤 3: Server2 发送 STARTTLS 范围给 Server1 ,包括验证机制和任何其他流特性:
   <stream:features>     <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>       <required/>     </starttls>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>KERBEROS_V4</mechanism>     </mechanisms>   </stream:features>
步骤 4: Server1 发送 STARTTLS 命令给 Server2:
   <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
步骤 5: Server2 通知 Server1 允许继续进行:
   <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
步骤 5 (或者): Server2 通知 Server1 TLS握手失败并关闭流:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>   </stream:stream>
步骤 6: Server1 和 Server2 尝试通过 TCP 完成 TLS 握手.
步骤 7: 如果 TLS 握手成功, Server1 初始化一个新的流给 Server2:
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 7 (或者): 如果 TLS 握手不成功, Server2 关闭 TCP 连接.
步骤 8: Server2 发送一个包含任何可用流特性的流头信息给 Server1 :
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='s2s_234' version='1.0'>   <stream:features>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>KERBEROS_V4</mechanism>       <mechanism>EXTERNAL</mechanism>     </mechanisms>   </stream:features>
步骤 9: Server1 继续进行 SASL 握手(第六章).

SASL 的使用

概览

XMPP 有一个验证流的方法,即XMPP特定的SASL(简单验证和安全层)[SASL]。SASL提供了一个通用的方法为基于连接的协议增加验证支持,而XMPP使用了一个普通的XML名字空间来满足SASL的需要。
以下规则应用于:
  1. 如果SASL协商发生在两台服务器之间,除非服务器宣称的DNS主机名得到解析,否则不能(MUST NOT)进行通信。(参见 服务器间的通信(第十四章第四节)).
  2. 如果初始化实体有能力使用 SASL 协商, 它必须(MUST)在初始化流的头信息中包含一个值为"1.0"的属性'version'。
  3. 如果接收实体有能力使用 SASL 协商, 它必须(MUST)在应答从初始化实体收到的打开流标签时(如果打开的流标签包含一个值为"1.0"的'version'属性),通过'urn:ietf:params:xml:ns:xmpp-sasl'名字空间中的<mechanisms/>元素声明一个或多个验证机制.
  4. 当 SASL 协商时, 一个实体不能(MUST NOT)在流的根元素中发送任何空格符号(匹配 production [3] content of [XML])作为元素之间的分隔符(在以下的SASL例子中任何空格符号的出现仅仅是为了增加可读性); 这条禁令帮助确保安全层字节的精确度。
  5. 当SASL握手时,在XML元素中使用的任何 XML 字符数据必须被编码成 base64, 编码遵循 RFC 3548 第三章的规定。
  6. 如果一个 简单名字"simple username" 规范被选定的SASL机制所支持,(比如, 这被 DIGEST-MD5 和 CRAM-MD5 机制支持但不被 EXTERNAL 和 GSSAPI 机制支持), 验证的时候初始化实体应该(SHOULD)在服务器间通信时提供 简单名字 自身的发送域(IP地址或包含在一个域标识符中的域名全称),在客户端与服务器之间通信时提供注册用户名(包含在一个XMPP节点标识符中的用户或节点名)。
  7. 如果初始化实体希望以另一个实体的身份出现并且SASL机制支持授权ID的传输,初始化实体在SASL握手时必须(MUST)提供一个授权ID。如果初始化实体不希望以另一个实体的身份出现,初始化实体在SASL握手时不能(MUST NOT)提供一个授权ID。在 [SASL] 的定义中,除非授权ID不同于从验证ID(详见[SASL])中得到的缺省的授权ID,初始化实体不能(MUST NOT)提供授权ID。如果提供了,这个授权ID的值必须(MUST)是<domain>的格式(对于服务器来说)或<node@domain>的格式(对于客户端来说).
  8. 在成功进行包括安全层的SASL握手之后,接收实体必须(MUST)丢弃任何从初始化实体得到的而不是从SASL协商本身获得的信息。
  9. 在成功进行包括安全层的SASL握手之后,初始化实体必须(MUST)丢弃任何从接收实体得到的而不是从SASL协商本身获得的信息。
  10. 参看 强制执行的技术 (第十四章第七节) ,了解关于必须(MUST)支持的机制.

叙述

一个初始化实体使用SASL和接收实体做验证的步骤如下:
  1. 初始化实体请求SASL验证,它发送一个打开的XML流头信息给接收实体,其'version'属性的值为"1.0".
  2. 在发送一个XML流头回复之后,接收实体声明一个可用的SASL验证机制清单;每个机制作为一个<mechanism/>元素,作为子元素包含在<mechanisms/>容器元素(其名字空间为'urn:ietf:params:xml:ns:xmpp-sasl')中,而<mechanisms/>则包含在流名字空间中的<features/>元素中。如果在使用任何验证机制之前需要使用TLS(见第五章),接收实体不能(MUST NOT)在TLS握手之前提供可用的SASL验证机制清单。如果初始化实体在优先的TLS协商过程中呈现了一个合法的证书,接收实体应该(SHOULD)在SASL握手中提出一个SASL外部机制给初始化实体,尽管这个外部机制可以(MAY)在其它环境下提供。
  3. 初始化实体发送一个符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<auth/>元素(其中包含了适当的'mechanism'属性值)给接收实体,以选择一个机制。如果这个机制支持或需要,这个元素可以(MAY)包含XML字符数据(在SASL术语中,即“初始化应答”);如果初始化实体需要发送一个零字节的初始化应答,它必须(MUST)传输一个单独的等号作为应答,这表示应答有效但不包含数据。
  4. 如果必要,接收实体向初始化实体发送一个符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<challenge/>元素来发出挑战;这个元素可以(MAY)包含XML字符数据(必须按照初始化实体选择的SASL机制进行一致性运算)。
  5. 初始化实体向接收实体发送符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<response/>元素作为应答;这个元素可以(MAY)包含XML字符数据(必须按照初始化实体选择的SASL机制进行一致性运算)。
  6. 如果必要,接收实体发送更多的挑战给初始化实体,初始化实体发送更多的回应。
这一系列的 挑战/应答 组,持续进行直到发生以下三件事中的一件为止:
  1. 初始化实体向接收实体发送符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<abort/>元素以中止握手。在接收到<abort/>元素之后,接收实体应该(SHOULD)允许一个可配置的但是合理的重试次数(至少2次),然后它必须(MUST)终止TCP连接;这使得初始化实体(如一个最终用户客户端)能够容忍可能不正确的credentials(如密码输入错误)而不用强制重新连接。
  2. 接收实体向初始化实体发送符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<failure/>元素以报告握手失败(详细的失败原因应该在<failure/>的一个适当的子元素中沟通,在第六章第四节中的SASL Errors中定义)。如果失败的情况发生了,接收实体应该(SHOULD)允许一个可配置的但是合理的重试次数(至少2次), 然后它必须(MUST)终止TCP连接;这使得初始化实体(如一个最终用户客户端)能够容忍可能不正确的credentials(如密码输入错误)而不用强制重新连接。
  3. 接收实体向初始化实体发送符合'urn:ietf:params:xml:ns:xmpp-sasl'名字空间的<success/>元素以报告握手成功;如果所选择的SASL机制要求,这个元素可以(MAY)包含XML字符数据(见SASL术语,“成功的额外数据”)。接收到<success/> 元素之后,初始化实体必须(MUST)发送一个打开的XML流头信息给接收实体以发起一个新的的流(不需要先发送一个 </stream>标签,因为在发送和接收到<success/>元素之后,接收实体和初始化实体必须确认原来的流被关闭了)。从初始化实体接收到新的流头信息之后,接收实体必须(MUST)发送一个新的流头信息给初始化实体作为回应,附上任何可用的特性(但不包括 STARTTLS 和 SASL 特性)或一个空的 <features/> 元素(这表示没有更多的特性可用);任何没有在本文定义的附加特性必须(MUST)在XMPP的相关扩展中定义。

SASL 定义

[SASL]的必要条件要求通过协议定义来提供以下信息:
service name(服务名): "xmpp"
initiation sequence(开始序列): 当初始化实体提供一个打开的XML流头信息并且接收实体善意回应之后,接收实体提供一个可接受的验证方法清单。初始化实体从这个清单中选择一个方法,把它作为 <auth/> 元素的 'mechanism' 属性的值发送给接收实体,也可以选择发送一个初始化应答以避免循环。
exchange sequence(交换序列): 挑战和回应的交换,从接收实体发送给初始化实体的 <challenge/> 元素和从初始化实体发送给接收实体的 <response/> 元素信息。接收实体通过发送 <failure/>元素报告失败,发送<success/>元素报告成功;初始化实体通过发送<abort/> 元素中止交换。成功的协商之后,两边都认为原来的XML流已经关闭并且都开始发送新的流头信息。
security layer negotiation(安全层协商): 安全层在接收实体发送 <success/> 元素的关闭字符">"之后立刻生效,在初始化实体发送 <success/> 元素的关闭字符">"之后也立刻生效。层的顺序是 [TCP],[TLS],然后是 [SASL],然后是 [XMPP]。
use of the authorization identity(授权ID的使用): 授权ID可在xmpp中用于表示一个客户端的非缺省的<node@domain>,或一个服务器的<domain> 。

SASL 错误

SASL相关的错误条件定义如下:
  • <aborted/> -- 接收实体认可由初始化实体发送的<abort/>元素;在回应一个<abort/>元素时发送。
  • <incorrect-encoding/> -- 由初始化实体提供的数据无法处理,因为[BASE64]编码不正确(例如,因为编码不符合[BASE64]的第三章); 在回应一个包含初始化响应数据的<response/> 元素或<auth/>元素时发送.
  • <invalid-authzid/> -- 由初始化实体提供的授权id是非法的,因为它的格式不正确或初始化实体无权给那个ID授权;在回应一个包含初始化响应数据的<response/> 元素或<auth/>元素时发送。
  • <invalid-mechanism/> -- 初始化实体不能提供一个机制活、或请求一个不被接受实体支持的机制;在回应一个<auth/>元素时发送。
  • <mechanism-too-weak/> -- 初始化实体请求的机制比服务器策略对它的要求弱;在回应一个包含初始化响应数据的<response/> 元素或<auth/>元素时发送。
  • <not-authorized/> -- 验证失败,因为初始化实体没有提供合法的credentials(这包括但不限于未知用户名等情形);在回应一个包含初始化响应数据的<response/> 元素或<auth/>元素时发送。
  • <temporary-auth-failure/> -- 验证失败,因为接收实体出现了临时的错误;在回应一个<response/> 元素或<auth/>元素时发送。

客户端-服务器 示例

以下例子展示了一个客户端和一个服务器使用SASL作验证的数据流,通常是在成功的TLS握手之后(注意:以下显示的替代步骤仅用于举例说明协议的失败情形;它们不够详尽也不需要由例子中的数据传送来触发)。
步骤 1: 客户端初始化流给服务器:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 2: 服务器向客户端发送流标签作为应答:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_234' from='example.com' version='1.0'>
步骤 3: 服务器通知客户端可用的验证机制:
   <stream:features>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>PLAIN</mechanism>     </mechanisms>   </stream:features>
步骤 4: 客户端选择一个验证机制:
   <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
步骤 5: 服务器发送一个 [BASE64] 编码的挑战给客户端:
   <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgi   LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==   </challenge>
解码后的挑战信息是:
   realm="somerealm",nonce="OA6MG9tEQGm2hh",\   qop="auth",charset=utf-8,algorithm=md5-sess
步骤 5 (替代): 服务器返回一个错误给客户端:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <incorrect-encoding/>   </failure>   </stream:stream>
步骤 6: 客户端发送一个[BASE64]编码的回应这个挑战:
   <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0i   T0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAw   MDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20i   LHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNo   YXJzZXQ9dXRmLTgK   </response>
解码后的回应信息是:
   username="somenode",realm="somerealm",\   nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\   nc=00000001,qop=auth,digest-uri="xmpp/example.com",\   response=d388dad90d4bbd760a152321f2143af7,charset=utf-8
步骤 7: 服务器发送另一个[BASE64]编码的挑战给客户端:
   <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=   </challenge>
解码后的挑战信息是:
   rspauth=ea40f60335c427b5527b84dbabcdfffd
步骤 7 (或者): 服务器返回一个错误给客户端:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <temporary-auth-failure/>   </failure>   </stream:stream>
步骤 8: 客户端应答这个挑战:
   <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
步骤 9: 服务器通知客户端验证成功:
   <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
步骤 9 (或者): 服务器通知客户端验证失败:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <temporary-auth-failure/>   </failure>   </stream:stream>
步骤 10: 客户端发起一个新的流给服务器:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 11: 服务器发送一个流头信息回应客户端,并附上任何可用的特性(或空的features元素):
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_345' from='example.com' version='1.0'>   <stream:features>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>     <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>   </stream:features>

服务器-服务器 示例

以下例子展示了一个服务器和另一个服务器使用SASL作验证的数据流,通常是在成功的TLS握手之后(注意:以下显示的替代步骤仅用于举例说明协议的失败情形;它们不够详尽也不需要由例子中的数据传送来触发)。
步骤 1: 服务器1 发起一个流给 服务器2 :
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 2: 服务器2 回应一个流标签给 服务器1:
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='s2s_234' version='1.0'>
步骤 3: 服务器2 通知 服务器1 可用的验证机制:
   <stream:features>     <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>       <mechanism>DIGEST-MD5</mechanism>       <mechanism>KERBEROS_V4</mechanism>     </mechanisms>   </stream:features>
步骤 4: 服务器1 选择一个验证机制:
   <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
步骤 5: 服务器2 发送一个[BASE64]编码的挑战给 服务器1:
   <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9   ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz   </challenge>
解码后的挑战信息是:
   realm="somerealm",nonce="OA6MG9tEQGm2hh",\   qop="auth",charset=utf-8,algorithm=md5-sess
步骤 5 (替代): 服务器2 返回一个错误给 服务器1:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <incorrect-encoding/>   </failure>   </stream:stream>
步骤 6: 服务器1 发送一个[BASE64]编码的回应这个挑战:
   <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   dXNlcm5hbWU9ImV4YW1wbGUub3JnIixyZWFsbT0ic29tZXJlYWxtIixub25j   ZT0iT0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5j   PTAwMDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5v   cmciLHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3   LGNoYXJzZXQ9dXRmLTgK   </response>
解码后的应答信息是:
   username="example.org",realm="somerealm",\   nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",\   nc=00000001,qop=auth,digest-uri="xmpp/example.org",\   response=d388dad90d4bbd760a152321f2143af7,charset=utf-8
步骤 7: 服务器2 发送另外一个[BASE64]编码的挑战给 服务器1:
   <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>   cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=   </challenge>
解码后的挑战信息是:
   rspauth=ea40f60335c427b5527b84dbabcdfffd
步骤 7 (或者): 服务器2 返回一个错误给 服务器1:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <invalid-authzid/>   </failure>   </stream:stream>
步骤 8: 服务器1 回应挑战:
   <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
步骤 8 (或者): 服务器1 中止协商:
   <abort xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
步骤 9: 服务器2 通知 服务器1 验证成功:
   <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
步骤 9 (或者): 服务器2 通知 服务器1 验证失败:
   <failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>     <aborted/>   </failure>   </stream:stream>
步骤 10: 服务器1 重新发起一个新的流给 服务器2:
   <stream:stream xmlns='jabber:server' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
步骤 11: 服务器2 发送一个流头信息应答 服务器1 ,并附上任何可用的特性(或一个空的features元素).:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='s2s_345' version='1.0'>   <stream:features/>

资源绑定

在和接收实体完成 SASL 协商(第六章)之后,初始化实体可能(MAY)想要或者需要绑定一个特定的资源到流上.通常这仅适用于客户端: 为了满足本文定义的寻址格式(第三章)和节传输规则(第十章),客户端<node@domain>必须(MUST)拥有一个相关的资源ID(由服务器生成或由客户端程序提供);以确保在流上使用的地址是一个“全JID”(<node@domain/resource>)。
接收到一个成功的SASL握手之后,客户端必须(MUST)发送一个新的流头信息给服务器,服务器必须(MUST)返回一个包含可用的流特性列表的头信息。特别是,在成功的SASL握手之后如果服务器需要客户端绑定一个资源,它必须(MUST)在握手成功之后(而不是之前)发送给客户端的应答流特性中包含一个空的符合'urn:ietf:params:xml:ns:xmpp-bind'名字空间的<bind/>元素。:
服务器向客户端声明资源绑定特性:
   <stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_345' from='example.com' version='1.0'>   <stream:features>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>   </stream:features>
收到要求资源绑定的通知后,客户端必须(MUST)通过发送一个符合'urn:ietf:params:xml:ns:xmpp-bind'名字空间的“set”类型的IQ节(参见 IQ 语义 (第九章第二节第三小节))给服务器来绑定一个资源到流中。
如果客户端端希望允许服务器给自己生成一个资源ID,它可以发送一个包含空的<bind/>元素的“set”类型的IQ节。:
客户端请求服务器绑定资源:
   <iq type='set' id='bind_1'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>   </iq>
一个支持资源绑定的服务器必须(MUST)自动生成一个资源ID给客户端。一个由服务器生成的资源ID对于那个<node@domain>必须(MUST)是唯一的。
如果客户端希望指定资源ID,它发送一个包含期望资源ID的“set”类型的 IQ节,把资源ID作为<bind/>元素下的<resource/>子元素的XML字符数据:
客户端绑定一个资源:
   <iq type='set' id='bind_2'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>       <resource>someresource</resource>     </bind>   </iq>
一旦服务器为客户端生成了一个资源ID或接受了客户端自己提供的资源ID,它必须(MUST)返回一个“result”类型的 IQ 节给客户端,这个节必须包含一个指明全JID的<jid/>子元素表示服务器决定连接的资源:
服务器通知客户端资源绑定成功:
   <iq type='result' id='bind_2'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>       <jid>somenode@example.com/someresource</jid>     </bind>   </iq>
一个服务器应该(SHOULD)接受客户端提供的资源ID,但是可以(MAY)用服务器生成的资源ID覆盖它;在这种情况下,服务器不应该(SHOULD NOT)返回一个错误信息一个(如<forbidden/>)给客户端,而应该(SHOULD)在上文所述的 IQ result 中返回生成的资源ID。
当一个客户端自行提供资源ID时,可能发生以下的节错误(参见 Stanza Errors (第九章第三节)):
- 提供的资源ID服务器无法处理,因为不符合资源字符规范Resourceprep(附录B)。
- 客户端不被允许绑定一个资源(如节点或用户已经达到允许连接的资源数限制)。
- 提供的资源ID已经被使用但是服务器不允许以同一个资源ID绑定多个连接。
协议对这些错误条件规定如下.
资源ID不能处理:
   <iq type='error' id='bind_2'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>       <resource>someresource</resource>     </bind>     <error type='modify'>       <bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>     </error>   </iq>
客户端不允许绑定一个资源:
   <iq type='error' id='bind_2'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>       <resource>someresource</resource>     </bind>     <error type='cancel'>       <not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>     </error>   </iq>
资源ID已经在使用:
   <iq type='error' id='bind_2'>     <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>       <resource>someresource</resource>     </bind>     <error type='cancel'>       <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>     </error>   </iq>
如果,在完成资源绑定步骤之前,客户端试图以符合'urn:ietf:params:xml:ns:xmpp-bind'名字空间的<bind/>子元素发送一个非IQ类型的XML节,服务器不能(MUST NOT)处理这个节,而应该(SHOULD)返回一个<not-authorized/>节错误信息给客户端.

服务器回拨

概览

Jabber协议接受的来自XMPP的包括“服务器回拨”方法,用于防治域名欺骗,使得欺骗XML节更为困难。服务器回拨不是一个安全机制,并且它的服务器身份认证结果很弱(参见服务器之间的通信(第十四章第四节)中关于这个方法的安全特性)。需要健壮的安全性的域名应该(SHOULD)使用TLS或SASL;细节参见服务器间的通信(第十四章第四节)。如果SASL用于服务器间通信,回拨就不需要用了(SHOULD NOT)。本文描述回拨的好处在于向后兼容现存的实现和部署。
服务器回拨方法由现存的DNS系统的存在而成为可能,因为一个服务器(通常)可以查询给定域的授权服务器。由于服务器回拨依赖于DNS,除非服务器宣称的DNS主机得到解析,域之间的通信无法进行(参见服务器间的通信(第十四章第四节))。
服务器回拨是单向性的,可在单一方向上对一个流进行(微弱的)身份验证.因为服务器回拨不是一个验证机制,通过回拨获得相互的认证是不可能的.因此,为了使得服务器之间的双向通信,服务器回拨必须(MUST)在每个方向上完成。
在服务器回拨中生成和检验密钥的方法必须(MUST)考虑计算被使用的主机名,由接收服务器生成的流ID,和只有授权服务器网络才知道的秘密。在服务器回拨中流ID是安全性的关键所以必须(MUST)是不可预知的和不可重复的(见[RANDOM]中关于使用随机数获得安全性的建议).
任何回拨协商过程中发生的错误必须(MUST)被当成一个流错误,并导致流以及相关的TCP连接的终止.可能发生的错误情况协议中描述如下.
以下术语适用:
  • 发起服务器 -- 尝试在两个域之间建立连接的那个服务器.
  • 接收服务器 -- 尝试验证发起服务期声称的域名的那台服务器.
  • 授权(权威?Authoritative) 服务器 -- 回答发起服务器声称的DNS主机名的服务器;基本上这应该是那台发起服务器,但是它也可能是一个在发起服务器网络中独立的服务器。

事件顺序

以下是回拨中的事件顺序的简介:
  1. 发起服务器和接收服务器建立一个连接。
  2. 发起服务器通过到连接发送一个 'key' 值给接收服务期。
  3. 接收服务器建立一个连接到授权服务器。
  4. 接收服务器发送一个相同的 'key' 值给授权服务器。
  5. 授权服务器回答这个key是否合法。
  6. 接收服务器通知发起服务器是否被验证通过。
我们用用以下图形展示这个事件流程(见附件s2s.png):

{image:img=s2s}

   发起服务器               接收服务器  -----------               ---------      |                         |      |   建立连接              |      | ----------------------> |      |                         |      |   发stream头            |      | ----------------------> |      |                         |      |   发stream头            |      | <---------------------- |      |                         |                         |   发回拨key             |                     授权服务器      | ----------------------> |                   -------------      |                         |                         |                                |   建立连接              |                                | ----------------------> |                                |                         |                                |   发stream头            |                                | ----------------------> |                                |                         |                                |   发stream头            |                                | <---------------------- |                                |                         |                                |   发验证请求            |                                | ----------------------> |                                |                         |                                |   发验证响应            |                                | <---------------------- |                                |      |  报告回拨结果           |      | <---------------------- |      |                         |

协议

服务器之间互动的细节协议如下:
1. 发起服务器和接受服务器建立TCP连接.
2. 发起服务器发送流头信息给接收服务器:
   <stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:server' xmlns:db='jabber:server:dialback'>
注意: 'to'和'from'属性在流的根元素是可选的(OPTIONAL). 其中包含的xmlns:db名字空间向接收服务器声明了发起服务器支持回拨. 如果名字空间不正确,接收实体必须(MUST)生成一个<invalid-namespace/>流错误条件并且终止XML流和相应的TCP连接.
3. 接收服务器应该(SHOULD)回送一个流头信息给发起服务器,为这次交互生成一个唯一性的ID :
   <stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:server' xmlns:db='jabber:server:dialback' id='457F9224A0...'>
注意: 'to'和'from'属性在流的根元素是可选的(OPTIONAL). 如果名字空间不正确,发起服务器必须生成一个<invalid-namespace/>流错误条件并且终止XML流和相应的TCP连接.也要注意,在这里接收服务器应该(SHOULD)应答但是可以(MAY)出于安全策略考虑只是悄悄地终止XML流和TCP连接;无论如何,如果接收服务器希望继续,它必须(MUST)回送一个流头信息给发起服务器.
4. 发起服务器发送一个回拨密钥给接收服务器:
   <db:result to='Receiving Server' from='Originating Server'>     98AF014EDC0...   </db:result>
注意: 这个密钥不由接收服务器检查,因为接收服务器在会话之间(between sessions)不保存发起服务器的信息. 这个由发起服务器生成的密钥必须(MUST)是基于接收服务器在上一步骤中提供的ID值,以及发起服务器与授权服务器共享的安全机制生成的。 如果 'to' 地址的值和接收服务器知道的主机名不匹配,接收服务器必须(MUST)生成一个<host-unknown/>流错误条件并且终止XML流和相应的TCP连接. 如果 'from' 地址和接收服务器已经建立的连接的域名相吻合,接收服务器必须(MUST)维护这个已经存在的连接,直到证明这个新的连接是合法的为止;另外,接收实体可以(MAY)选择生成一个<not-authorized/> 流错误条件给这个新的连接并且终止和新连接申请相关的XML流及相应的TCP连接.
5. 接收服务器向发起服务器声明的那个域建立一个 TCP 连接,作为结果它连接到授权服务器. (注意: 为了优化性能, 在这里一个实现可以(MAY)重用现有的连接.)
6. 接收服务器发送一个流头信息给授权服务器:
   <stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:server' xmlns:db='jabber:server:dialback'>
注意: 'to'和'from'属性在流的根元素是可选的(OPTIONAL). 如果名字空间不正确,授权服务器必须生成一个<invalid-namespace/>流错误条件并且终止XML流和相应的TCP连接.
7. 授权服务器发送流头信息给接收服务器:
   <stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:server' xmlns:db='jabber:server:dialback' id='1251A342B...'>
注意: 如果名字空间不正确,接收服务器必须生成一个<invalid-namespace/>流错误条件并且终止它和授权服务器之间的XML流和相应的TCP连接. 如果一个流错误发生在接收服务器和 授权服务器 之间,接收服务器必须(MUST)生成一个 <remote-connection-failed/> 流错误条件并且终止它和 发起服务器 之间的XML流和相应的TCP连接.
8. 接收服务器发送一个密钥检查请求给授权服务器:
   <db:verify from='Receiving Server' to='Originating Server' id='457F9224A0...'>     98AF014EDC0...   </db:verify>
注意: 现在这里已经有了主机名,第三步中接收服务器发送给发起服务器的ID,第四步中发起服务器发送给接收服务器的密钥. 基于这些信息, 加上授权服务器网络共享的安全信息, 这个密钥被证实了.任何可用于验证的办法都可以(MAY)用于生成密钥. 如果 'to' 地址的值和授权服务器知道的主机名不匹配,授权服务器必须(MUST)生成一个<host-unknown/>流错误条件并且终止XML流和相应的TCP连接. 打开这个连接时,如果 'from' 地址和接收服务器声明的主机名(或任何合法的域,如验证过的接收服务器的子域名或寄宿在接收服务器上的其他经过验证的域)不符,授权服务器必须(MUST)生成一个<invalid-from/>流错误条件并且终止XML流和相应的TCP连接.
9. 授权服务器检查密钥是否合法:
   <db:verify from='Originating Server' to='Receiving Server' type='valid' id='457F9224A0...'/>
   <db:verify from='Originating Server' to='Receiving Server' type='invalid' id='457F9224A0...'/>
注意: 如果 ID 和第三步中接收服务器提供的不符,接收服务器必须(MUST)生成一个<invalid-id/>流错误条件并且终止XML流和相应的TCP连接. 如果 'to' 地址的值和接收服务器知道的主机名不匹配,接收服务器必须(MUST)生成一个<host-unknown/>流错误条件并且终止XML流和相应的TCP连接. 打开这个连接时,如果 'from' 地址和发起服务器声明的主机名(或任何合法的域,如验证过的发起服务器的子域名或寄宿在发起服务器上的其他经过验证的域)不符,接收服务器必须(MUST)生成一个<invalid-from/>流错误条件并且终止XML流和相应的TCP连接. 在返回验证信息给接收服务器之后,授权服务器应该(SHOULD)终止它们之间的流.
10. 接收服务器通知发起服务器结果:
   <db:result from='Receiving Server'to='Originating Server' type='valid'/>
注意: 在这一个点上, 连接已经由 type='valid' 确认验证是通过了,还是没通过. 如果连接是非法的,接收服务器必须(MUST)终止XML流和相应的TCP连接. 如果连接是合法的, 数据可以从发起服务器发送由接收服务器读取;在此之前,所有发送给接收服务器的XML节应该(SHOULD)被丢弃.
进一步的结果是,接收服务器已经验证了发起服务器的ID,所以通过初始化流("initial stream",例如从发起服务器到接收服务器的流)发起服务器可以发送,接收服务器可以接受XML节. 为了使用应答流("response stream",例如从接收服务器到发起服务器的流)验证实体ID,回拨必须(MUST)在相对的两个方向上都完成.
在成功的回拨协商之后, 接收服务器应该(SHOULD)接受接下来发起服务器通过当前的合法连接发送的 <db:result/> 包 (例如, 发送给子域或其他寄宿在接收服务器上的主机名的验证请求); 这在单一方向上激活了原始合法连接的 "piggybacking" .

即使回拨协商成功了, 服务器仍然必须(MUST)检查从其他服务器接收的所有XML节的'from'和'to'属性; 如果一个节不符合这些限定, 收到这些节的服务器必须(MUST)生成一个<improper-addressing/> 流错误条件并终止XML流和相应的TCP连接. 而且, 一个服务器也必须(MUST)检查从其他的有合法域名的服务器的流中收到的节的'from'属性; 如果一个节不符合这一限定, 接收节的服务器必须(MUST)生成一个 <invalid-from/> 流错误条件并终止XML流和相应的TCP连接. 所有这些检查都用来帮助防止特定的节伪造行为.

文章出处:http://wiki.jabbercn.org/RFC3920

0 0