openfire TSL的运用与STARTTLS协商

openfire TSL的运用与STARTTLS协商

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 。如下:

    证书确认

    当一个XMPP点和另一个XMPP点安全的地通信, 它必须(MUST)确认对方终端的证书.有三种可能的情况:
    情形 #1: 点包含一个终端实体证书,以根证书的证书链中一环出现(见[X509]中的第六章第一节).
    情形 #2: 点的证书由一个对方不知道的证书授权.
    情形 #3: 点的证书由自己签名.
    在情形#1, 确认方必须(MUST)做以下两条之一:
    1. 根据[X509]的规则确认对方证书.然后证书应该(SHOULD)被对方接下来在[HTTP-TLS]中描述的规则反向确认预期的身份。但如果"xmpp"是subjectAltName扩展类型,则必须(MUST)使用证书中的显示的身份。如果这两项检查之一失败,用户导向的客户端必须(MUST)通知用户(客户端可以(MAY)给用户机会继续连接)或以一个坏证书的错误终止连接。自动客户端应该(SHOULD)终止连接(以一个坏证书错误)并在适当的日志中记录这个错误。自动客户端可以(MAY)提供一个配置设置成禁止检查,但同时必须(MUST)提供一个激活检查的配置。
    2. 点应该(SHOULD)出示证书给一个用户用于批准,包括完整的证书链.点必须(MUST)缓存这个证书(或一些其他不会忘记的表达方式比如一个哈希值).在将来的连接中,点必须(MUST)展示相同的证书并且如果改变了证书必须(MUST)通知用户.
    在情形#2 和情形#3, 实现应该(SHOULD)执行上述第二条.
  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 ,如下:

    强制实现的技术

    最低要求, 所有实现必须(MUST)支持以下机制:
    对于验证: SASL [DIGEST-MD5] 机制
    对于机密性: TLS (使用 TLS_RSA_WITH_3DES_EDE_CBC_SHA 密码)
    对于两者: TLS 加 SASL EXTERNAL(使用 TLS_RSA_WITH_3DES_EDE_CBC_SHA 密码支持客户端证书)

用于 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 握手 见:http://blog.csdn.net/love254443233/article/details/7906918.

服务器-服务器示例

以下例子展示两个服务器之间使用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 握手,见 http://blog.csdn.net/love254443233/article/details/7906918.

STARTTLS协商

STARTTLS基础

XMPP包含了一个方法来保护流的安全使其免于被篡改和窃听. 这个通道加密的方法使用传输层安全 TLS 协议, 特别是"STARTTLS"扩展,这个扩展是以 USINGTLS中描述的 IMAP, POP3, 和 ACAP 协议中的类似扩展为蓝本的. STARTTLS扩展的XML命名空间是 'urn:ietf:params:xml:ns:xmpp-tls'.

支持

在XMPP客户端和服务器的实现中必须支持STARTTLS. 一个给定布署的管理员可以指定 客户端-服务器通讯 和/或 服务器-服务器通讯 中TLS是强制协商的. 一个初始化实体应该在开始SASL验证之前使用TLS保护和接收方之间的流的安全.

流协商规则

强制协商

如果接收方实体只声明了STARTTLS特性或接收方实体包含了5.4.1所述的<required/>子元素, 双方必须确保TLS是强制协商的. 如果TLS是强制协商的, 在流协商过程的初始化阶段接收方实体应该不(SHOULD NOT)声明支持任何STARTTLS以外的流特性, 因为在XMPP的层顺序中更多流特性可能依赖TLS的预先协商 (例如, 由接收方实体提供的特定的SASL机制将依赖于TLS是否完成协商).

重启

在TLS协商之后, 双方必须重启这个流.

数据格式

当STARTTLS协商时, 实体们不能(MUST NOT)在XML元素之间发送任何空格符号 (即, 由初始化实体发送的从'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的顶级<starttls/>元素的最后的字符, 到由接收方实体发送的'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的顶级<proceed/>元素的最后的字符). 这个禁令帮助确保适当的安全字节精度. 任何出现在本文提供的STARTTLS例子中的空格只是为了提高可读性.

TLS和SASL协商的顺序

如果初始化实体选择使用TLS, STARTTLS协商必须在SASL协商之前完成; 这个协商顺序对于帮助保护SASL协商期间发送的验证信息是必要的, 同时尽可能使用基于预先的TLS协商中提供的证书(或其他证件)的SASL EXTERNAL机制.

TLS重协商

TLS协议允许双方在一个 受TLS保护 的通道里初始化一个新的握手来建立新的加密参数(见 TLS‑NEG). 最常提及的案例如下:

  1. 刷新密钥
  2. 如TLS的6.1节所述封装TLS序列号.
  3. 在受保护的通道上先完成服务器验证再完成客户端验证以保护客户端证书.

因为在XMPP中建立一个流的代价相对低廉, 对于前面两个案例推荐使用XMPP流重置(如 4.9.3.16) 而不是执行TLS重协商.

第三个案例在TLS客户端(也可能是一个XMPP服务器)递交TLS证书给服务器时提高了安全特性. 如果和一个未验证的TLS服务器交换这类证书可能泄露隐私信息, 先完成让TLS客户端验证TLS服务器的TLS协商,再完成让TLS服务器验证TLS客户端的TLS协商,是适当的. 然而, 这个案例极为罕见,因为由一个扮演TLS客户端角色的XMPP服务器或XMPP客户端对外展现的证书几乎总是公开的(即, PKIX 证书), 所以在验证作为TLS服务器的XMPP服务器之前提供那些证书通常将不会泄露隐私信息.

作为结果, 鼓励实现者在他们的软件中支持TLS重协商之前小心地权衡它的开销和好处, 不鼓励扮演TLS客户端的XMPP实体尝试TLS重协商,除非已知要在TLS协商中发送的证书(或其他证件信息)是私有的.

对TLS重协商的支持是严格可选的. 然而, 支持TLS重协商的实现们必须实现和使用 TLS重协商扩展 TLS‑NEG.

如果一个不支持TLS重协商的实体察觉到一个重协商尝试, 那么它必须立刻关闭相关的TCP连接而不要返回任何流错误(因为这个违规可能发生在TLS层, 而不是XMPP层, 详见 13.3).

如果一个支持TLS重协商的实体察觉到一个未使用 TLS重协商扩展 TLS‑NEG 的TLS重协商尝试 , 那么它必须立刻关闭相关的TCP连接而不要返回任何流错误(因为这个违规可能发生在TLS层, 而不是XMPP层, 详见 13.3).

TLS扩展

一个流的双方可以在它自己的TLS协商时包含任何TLS扩展. 这是TLS层的事情, 不是XMPP层.

过程

流头和流特性交换

初始化实体如第三章所述解析接收实体的合格域名(FQDN), 打开一个到解析的IP地址和声明的端口的TCP连接, 并发送一个初始化流头给接收方流头.

I: <stream:stream
     from='[email protected]'
     to='im.example.com'
     version='1.0'
     xml:lang='en'
     xmlns='jabber:client'
     xmlns:stream='http://etherx.jabber.org/streams'>

接收方实体必须通过初始化实体打开的那个TCP连接发送一个应答流头给初始化实体.

R: <stream:stream
     from='im.example.com'
     id='t7AMCin9zjMNwQKDnplntZPIDEI='
     to='[email protected]'
     version='1.0'
     xml:lang='en'
     xmlns='jabber:client'
     xmlns:stream='http://etherx.jabber.org/streams'>

接着接收方实体必须发送流特性给初始化实体. 如果接收方实体支持TLS, 流特性必须包含一个支持STARTTLS协商的声明, 即, 一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<starttls/>元素.

如果接收方实体认为STARTTLS协商是强制协商的, <starttls/>元素必须包含一个空的<required/>子元素.

R: <stream:features>
     <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
       <required/>
     </starttls>
   </stream:features>

STARTTLS协商的初始化

STARTTLS命令

为了开始STARTTLS协商, 初始化实体发出STARTTLS指令(即, 一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<starttls/>元素)来指示接收方实体它希望开始一次STARTTLS协商以保护流.

I: <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

接收方实体必须以由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<proceed/>元素(继续进行的情况下)或<failure/>元素(失败的情况下)回复.

失败的情况

如果发生失败的情况, 接收方实体必须返回一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<failure/>元素, 关闭这个XML流, 并且终止当前的TCP连接.

R: <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
 
R: </stream:stream>

导致失败的情况包含但不限于以下几种:

  1. 初始化实体发送了一个异常的STARTTLS命令.
  2. 接收方实体在它的流特性中不提供STARTTLS特性 .
  3. 接收方实体因为内部错误而无法完成STARTTLS协商.
提示性备注: STARTTLS失败不会由TLS错误触发,例如 坏证书(bad_certificate)或 握手失败(handshake_failure), 它是由TLS协商本身生成和处理的,参见TLS.

如果发生了失败的情况, 初始化实体可以尝试重连,参见 3.3.

继续进行的情况

如果发生继续进行的情况, 接收方实体必须返回一个由'urn:ietf:params:xml:ns:xmpp-tls'命名空间限定的<proceed/>元素.

R: <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

接收方实体在发送了<proceed/>元素的关闭字符'>'之后必须认为TLS协商已经立刻开始了. 初始化实体在从接收方实体接收到<proceed/>元素的关闭字符'>'之后必须认为TLS协商已经立刻开始了.

实体现在继续进行TLS协商,如下一节所述.

TLS协商

规则

为了在TCP连接上完成TLS协商, 实体们必须跟随以下定义于TLS的过程 .

以下规则适用于:

  1. 实体们在TLS协商完成之前不能(MUST NOT)发送任何其他XML数据.
  2. 当使用定义于13.8的任何强制实现(MTI)的密码组时, 接收方实体必须出示一个证书.
  3. 所以证书相互验证是有可能的, 接收方实体应该发送一个证书请求给初始化实体, 而初始化实体应该发送一个证书给接收方实体(但是由于隐私的原因可能选择在接收方实体已经被初始化实体验证之后才发送自己的证书).
  4. 接收方实体应该基于包含在初始化流头中的'to'属性中的域部分选择哪个证书来展示(本质上, 这个域部分功能上等同于服务器名称指示 定义于TLS‑EXT).
  5. 为了确定TLS协商是否成功, 初始化实体必须尝试根据13.7.2定义的证书验证程序来验证接收方实体的证书.
  6. 如果初始化实体出示了一个证书, 接收方实体也必须尝试根据13.7.2定义的证书验证程序来验证初始化实体的证书.
  7. 随着TLS协商成功, 所有双方传送的更多的数据必须被协商的算法,密钥和秘密来保护(即, 加密, 完整性保护, 或都依赖于使用的密码组).
安全警告: 关于13.8提到的密码组必须被TLS所支持; 自然的, 其他密码组也可以被支持.
TLS失败

如果TLS协商结果是失败, 接收方实体必须终止该TCP连接.

在终止该TCP连接之前,接收方实体不能(MUST NOT)发送关闭标签</stream>(因为失败可能发生在TLS层, 而不是XMPP层,参见13.3所述).

初始化实体可以如3.3所述尝试重连, 尝试使用或不使用TLS协商(依照本地服务策略, 用户配置的偏好, 等等).

TLS成功

如果TLS协商是成功的, 那么实体们必须继续如下步骤.

1. 初始化实体必须忽略TLS生效之前在TCP上的不安全情况下从接收方实体收到的任何信息(例如, 接收方实体的'from'地址或从接收方实体收到的流ID以及流特性).
2. 接收方实体必须忽略TLS生效之前在TCP上的不安全情况下从初始化实体收到的任何信息(例如, 初始化实体的'from'地址).
3. 初始化实体必须通过加密的连接发送一个新的初始化流头给接收方实体(如4.3.3定义的, 在发送新的初始化流头之前初始化实体必须发送一个关闭标签</stream>, 因为接收方实体和初始化实体必须确定旧的流在TLS协商成功之后被替代了).
    I: <stream:stream
         from='[email protected]'
         to='im.example.com'
         version='1.0'
         xml:lang='en'
         xmlns='jabber:client'
         xmlns:stream='http://etherx.jabber.org/streams'>
4. 接收方实体必须通过加密连接以一个新的应答流头来应答(为此它必须生成一个新的流ID而不是重用旧的流ID).
    R: <stream:stream
         from='im.example.com'
         id='vgKi/bkYME8OAj4rlXMkpucAqe4='
         to='[email protected]'
         version='1.0'
         xml:lang='en'
         xmlns='jabber:client'
         xmlns:stream='http://etherx.jabber.org/streams'>
5. 接收方实体也必须发送流特性给初始化实体, 不能(MUST NOT)包含STARTTLS特性但是应该包含SASL流特性,如第六章(特别是6.4.1中关于为什么不在这里提供SASL流特性的新的原因).
    R: <stream:features>
         <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
           <mechanism>EXTERNAL</mechanism>
           <mechanism>SCRAM-SHA-1-PLUS</mechanism>
           <mechanism>SCRAM-SHA-1</mechanism>
           <mechanism>PLAIN</mechanism>
         </mechanisms>
       </stream:features>

转自  http://www.verydemo.com/demo_c284_i3105.html


你可能感兴趣的:(openfire TSL的运用与STARTTLS协商)