MSNP18协议分析(二)--- MSN登录身份认证

这一篇开始主要介绍MSN登录部分的协议分析,总体来说,登陆这一块是整个MSNP协议的一大块,也是比较复杂的一部分。整个登录的过程主要包括:连接服务器,身份验证,获取用户信息和联系人列表,把联系人列表发送给服务器,发送个人信息和状态,上线通知。我也打算按照登录的顺序去进行介绍。

这里我主要关注的是如何成功的登录,但是因为大多数都是抓包,也没有权威的官方资料参考,所以不能保证完全正确和详细,有些也不能给出合理的解释,但还是尽可能的介绍登录中会出现的各种情况,以及登录中用到的命令。

在使用命令之前,我们需要注意的时,普通命令后面都带有/r/n结尾。而palyload命令是根据数据长度找到结尾。我们在发送时必须注意这一点,如果发送的数据没有结尾标识,服务器会断开连接。另外发送的命令都需要以UTF8格式发送。


.

一 连接服务器

登录最终的目的是登录到NS服务器,与之建立连接并进行交互。一直到用户退出或注销时,才断开与服务器的连接。在登录到NS服务器之前,我们需要先连接到DS服务,并获得NS服务器的地址。

.

1 连接到DS服务器

DS服务器的地址是:messenger.hotmail.com ,端口号是1863在通过Socke连接之前需要对这个域名进行解析,而且解析的IP是不固定的,所以不建议使用固定的IP去连接DS服务器。以下是MSN Live 2009登录时和DS服务器之间的交互数据。

>>>VER 1 MSNP18 MSNP17 CVR0/r/n

<<<VER 1 MSNP18/r/n

>>>CVR 2 0x0804 winnt 5.1 i386 MSNMSGR 14.0.8089.0726 msmsgstest@live.cn/r/n

>>>USR 3 SSO Itest@live.cn/r/n

<<<CVR 2 14.0.8089 14.0.8089 14.0.8089 http://msgruser.dlservice.microsoft.com/download/0/9/7/0974F7CD-D082-46FE-922D-806670345793/zh-chs/wlsetup-cvr.exehttp://download.live.com/?sku=messenger/r/n

<<<XFR 3 NS 207.46.124.241:1863 U D/r/n

VER命令:

这是和DS服务器发送的第一条消息,他是告诉服务器客户端支持的MSNP版本。MSNP版本功能在上一篇文章有所介绍,发送的版本号可以是多个版本。发送的版本号是区分大小写的。最后一个参数是CVR0,他实际是(CVQ, VER) 的命令集合,用来设置客户端版本,使得客户端能够升级。但具体作用不详,不发送也是可以的。最后要注意的是最后一个参数结尾要带上"/r/n"。服务器在接受到命令后,会从我们发送的协议版本中选一个他支持的最高本版,如果我们发送的协议版本他不支持,就返回一个0。目前最新的协议支持到了MSNP21。

.

CVR命令:

这是是CVR命令的格式CVR trid lcid osName osVersion processorType clientName clientVersion [brandId] [userHandle]。聪从名字就能看出每个参数的作用。 lcid是地区编码,0x804表示的是中国。 clientVersion我们可以根据MSNP对应的官方版本来确定; brandID官方客户端是msmsgs ,最后一个就是登陆用户的账号。服务器会返回给你版本信息,最新的MSN下载地址,这些可以无视,我们又不是用MS的客户端。哈

.

USR命令:

USR命令是在登录过程中用来身份验证的一个命令,会多次使用,在连接DS服务器时比较简单。第一个参数是SSO,表示身份验证方式。从MSNP15开始就是用此方法验证,而之前的版本使用TWN。所以要支持多个协议方式登陆,首先要区分身份验证的方式。第二个参数是 (I = Initiate, S = Respond to Challenge). 我们这里第一次使用,发送I,最后一个参数还是用户的账号。USR还有多种用法,后面用到时继续介绍。

.

XFR命令:

服务器返回的XFR命令的作用是通知你NS服务器的地址,另一个作用就是在聊天时返回给你一个SB服务器的地址。第一个参数NS表示这是发送的NS地址,第二个参数就是服务器的IP地址和端口号。最后的U D表示什么不知道。接受到XFR后,DS服务器会自动断开SOCKET连接。这里XFR是否会不返回NS地址,不太清楚,起码目前没有发生过,当然程序中还是需要对这种情况进行处理。

.

以上就是连接DS服务的全部过程,介绍了几个命令的用法。要注意的是,这里并没有太多的介绍可能返回的错误。比如你已经登陆了,在发送这里的USR命令就会出错;如果发送消息过于频繁也会出错等等。但是一般,我们不需要太过关注这些,而且错误情况太多也根本关注不过来。后面我会给出所有服务器返回的错误代码的列表,实际出现问题时在去解决。这里我们只关注登录过程。需要注意的是发送命令不一定需要发送一条,接受一条才能在发送一条。有些命令可以不需要等待上一条返回就发送的。

.

2 连接到NS服务器

从XFR获得到了NS服务器的IP地址很端口号以后,我们就能使用SOCKET连接到NS服务器了。

>>>VER 1 MSNP18 MSNP17 CVR0/r/n


>>>CVR 2 0x0804 winnt 5.1 i386 MSNMSGR 14.0.8089.0726 msmsgs test@live.cn/r/n


>>>USR 3 SSO I test@live.cn/r/n

<<<VER 1 MSNP18/r/n


<<<CVR 2 14.0.8089 14.0.8089 14.0.8089/r/n http://msgruser.dlservice.microsoft.com/download/0/9/7/0974F7CD-D082-46FE-922D-806670345793/zh-chs/wlsetup-cvr.exe http://download.live.com/?sku=messenger


<<<GCF 0 5521/r/n<Policies>省略</Policies>

<<<USR 3 SSO S MBI_KEY_OLD kVJy53UCBCupSu09gfE9rH47mtDGzmINva5/vEiYJIfxft+an0igQBC445kNyIkV/r/n

以上是MSN 2009连接NS服务时发送的前3条命令,我们可以看到这里没有等待上一条返回,而是一起发送了。这里的3条命令和DS服务器发送的一样,这里就不在过冬介绍了。但是发现我们发送的USR命令后,返回的和DS时不一样了。

GCF命令:

此命令是从服务器获得配置文件信息,可以看他是一个playLoad命令,命令后面是ID好和数据长度。数据是使用XML存放。 但是从MSNP13开始此命令已经不在使用,所以我么可以不处理这一条命令。

.

USR命令:

在前面我们发送过USR命令,在这里我们收到了服务器发送来的USR命令。这里我们要关注的是MBI_KEY_OLD 已经后面的一传值。这个就是SSO认证过程中需要用到的KEY。这里返回的类型可能是MBI、MBI_SSL 、MBI_KEY_OLD等类型。

.

3 断开服务器连接

断开服务器连接有多种方式,对于NS服务器,我们不需要主动断开,在获得XFR之后,服务器会主动断开SOCKET连接。对于NS服务器,我们退出时可以发送OUT命令,这个命令不需要ID和任何参数,服务器接收到OUT之后就会断开连接,这是一种比较好的断开方法。对于SB服务器一样,我们也只需要发送一个OUT命令。

我们还可以直接断开客户端的SOCKET连接,但是这样服务器不一定能马上改变我们的状态,好友也不一定马上能看到我们下线或退出对话。而如果我们发送的命令不正确,有些情况,服务求就会马上断开连接。有一些错误服务器在断开之前会返回错误代码。

.

OUT命令:

我们能给服务器发送OUT表示我们退出,服务器有时也会在断开我们之前发送OUT通知我们。OUT [Reason]这是服务器发送OUT给我们时候的格式。当收到的与原因是OTH时,表示我们被其他登陆点注销,这可能是其他登陆点不支持多点登陆,也可能是被其他登陆点选择注销;当收到原因是RCT <delay> 的时候,表示服务器断开,指定的时间之后可以进行重连;SSD则表示服务器挂了;TME则表示登陆的终端太多,我们被服务器kick out了。我们可以根据不同情况进行处理。

.

UUN命令:UUN trid receiverHandle[;EPID] appID size/r/nBODY

从MSNP16之后,开始支持多点登陆,我们可以注销掉其他登陆点的MSN。使用的命令就是UUN。这个命令作用是发送客户端1对1的通知消息。使用这个命令注销其他登陆点只是其中的一个功能。第一个参数要通知的客户端用户的账号,第二个参数是EPID,这个是可选的。支持多点登陆的版本,每个登陆点会有一个GUID来表示终端。第三个参数是操作类型ID,我们注销其他登陆点使用4;最后是发送BODY的大小;注销其他终端,官方服务发送的是大小为14的一串字符ngoawyplzthxbye,没有尝试能否使用其他字符,我认为应该是可以的。服务器接受到这个消息后,会根据EPID向其他客户端发送OUT OTH命令。注意发送EPID时不要少了前面的分号。 发送成功服务器会返回UUN trid OK。

App IDDescription
0Debug App ID
12-share
2Voice conversation invites (never sent in UUN, only UBN)
3P2P bootstrapping
4Log off my remote endpoints
5Closing the conversation window
6Contact List Update
7Clear RL Prompt
8Forward non-session message (i.e. Offline IM)
9RESERVED
10TURN bridge notification
11Meta-data channel for audio calls
12Signaling channel for Voice over MSNP

以上是在UUN命令中使用到的AppID编号,同样这个编号也在UBN命令中使用。

UBN命令:UBN senderHandle;EPID appID size/r/nBody (>=MSNP 16)

在MSNP小于16的版本中,没有EPID这一项目。而这个命令从MSNP13开始支持。和UUN一样,他们的body内容最大长度为6144个字节,使用ASCII编码。这个命令接受到的可能是其他终端对UUN的返回结果,也可能是服务器发送来的邀请。

.

.

二 身份验证

从MSNP15开始,微软采用了新的身份认证方式SSO(Single Sign-On)。在MSN整个使用过程中,并不仅仅是登录需要身份验证,我们从WebService取得联系人列表,个人信息,用户头像,离线消息等等都需要身份验证。于是在登录的时候,我们进行身份验证的请求,服务会发送给我们一些列的ticket,他们对应了不同的服务请求。所以在开始登陆之前我们必须进行SSO认证,获得所需的ticket。SSO认证是通过WebServices服务完成的,SSO认证的地址为:https://login.live.com/RST.srf ,需要注意的是对于msn。com结尾的账号,需要提交到 https://msnia.login.live.com/pp550/RST.srf 进行认证。目前好像无法注册msn.com,也没有可用的账号,所以无法测试。

.

1 SSO认证方式

  1. <Envelopexmlns="http://schemas.xmlsoap.org/soap/envelope/"
  2. xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
  3. xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
  4. xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
  5. xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  6. xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
  7. xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc"
  8. xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">
  9. <Header>
  10. <ps:AuthInfo
  11. xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL"
  12. Id="PPAuthInfo">
  13. <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>
  14. <ps:BinaryVersion>4</ps:BinaryVersion>
  15. <ps:UIVersion>1</ps:UIVersion>
  16. <ps:Cookies></ps:Cookies>
  17. <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>
  18. </ps:AuthInfo>
  19. <wsse:Security>
  20. <wsse:UsernameTokenId="user">
  21. <wsse:Username>email@live.com</wsse:Username>
  22. <wsse:Password>PasswordGoesHere</wsse:Password>
  23. </wsse:UsernameToken>
  24. </wsse:Security>
  25. </Header>
  26. <Body>
  27. <ps:RequestMultipleSecurityTokens
  28. xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL"
  29. Id="RSTS">
  30. <wst:RequestSecurityTokenId="RST0">
  31. <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
  32. <wsp:AppliesTo>
  33. <wsa:EndpointReference>
  34. <wsa:Address>http://Passport.NET/tb</wsa:Address>
  35. </wsa:EndpointReference>
  36. </wsp:AppliesTo>
  37. </wst:RequestSecurityToken>
  38. <wst:RequestSecurityTokenId="RSTn">
  39. <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
  40. <wsp:AppliesTo>
  41. <wsa:EndpointReference>
  42. <wsa:Address>domain</wsa:Address>
  43. </wsa:EndpointReference>
  44. </wsp:AppliesTo>
  45. <wsse:PolicyReferenceURI="policy parameter"></wsse:PolicyReference>
  46. </wst:RequestSecurityToken>
  47. ...
  48. ...
  49. </ps:RequestMultipleSecurityTokens>
  50. </Body>
  51. </Envelope>

以上XML就是我们要提交到WebService的SOAP格式。首先我们需要填充自己的账号和密码到XML中。我们看到Body中有<wst:RequestSecurityToken Id="RSTn">...</wst:RequestSecurityToken>这样一个块,每一个代表一个你想要进行身份验证的域。RST0是必须的,我们添加对应的身份认证时,只需要构造从RST1开始节点块。这样你可以只提交一次,就获得多个不同的域的身份认证。 下面列出了所有域的相关信息。

RSTDimain域Policy RefPurpose
RST0http://Passport.NET/tb作用不明,但是必须包含才能请求成功
RST1messengerclear.live.comNS USR命令获得的Authentication for messenger.
目前在登录时使用
RST2messenger.msn.com?id=507Messenger website authentication。
目前在获得离线消息时使用
RST3contacts.msn.comMBI (used in WLM 8.5.1288.816)Authentication for the Contact server.
目前在操作联系人时使用
RST4messengersecure.live.comMBI_SSL未知
RST5spaces.msn.com spaces.live.comMBIAuthentication for the Windows Live Spaces
应该是登录到空间使用
RST6livecontacts.live.comMBILive Contacts API, a simplified version of the Contacts SOAP service
目前没有使用到
RST7storage.live.comMBIStorage REST API
目前获取用户头像时使用

我们只需要根据上表的内容,填充自己需要的域节点的内容,提交到服务器,就可以获得对应服务的ticket。RTS1的Policy Ref是我们前面USR命令所获得的MBI_KEY_OLD,前面说过了,这个参数可能有MBI、MBI_SSL等类型,所以要根据USR返回值填如XML中。网上有些地方是在NS连接前去进行SSO认证,这个是不正确的,因为我们还没获得Policy Ref。虽然目前基本发送的都是MBI_KEY_OLD。然后服务器会返回给我们如下的XML:

[c-sharp] view plain copy print ?
  1. <S:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  2. <S:Header>
  3. There is really data here, butfor space it has be removed
  4. </S:Header>
  5. <S:Body>
  6. <wst:RequestSecurityTokenResponseCollection
  7. xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
  8. xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust"
  9. xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
  10. xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  11. xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
  12. xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
  13. xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
  14. <wst:RequestSecurityTokenResponse>
  15. <wst:TokenType>urn:passport:legacy</wst:TokenType>
  16. <wsp:AppliesTo xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
  17. <wsa:EndpointReference>
  18. <wsa:Address>http://Passport.NET/tb</wsa:Address>
  19. </wsa:EndpointReference>
  20. </wsp:AppliesTo>
  21. <wst:LifeTime>
  22. <wsu:Created>2006-12-06T05:12:10Z</wsu:Created>
  23. <wsu:Expires>2006-12-07T05:12:10Z</wsu:Expires>
  24. </wst:LifeTime>
  25. <wst:RequestedSecurityToken>
  26. <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"
  27. Id="BinaryDAToken0"
  28. Type="http://www.w3.org/2001/04/xmlenc#Element">
  29. <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc">
  30. </EncryptionMethod>
  31. <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  32. <ds:KeyName>http://Passport.NET/STS</ds:KeyName>
  33. </ds:KeyInfo>
  34. <CipherData>
  35. <CipherValue>
  36. cipher data you don't need to worry about
  37. </CipherValue>
  38. </CipherData>
  39. </EncryptedData>
  40. </wst:RequestedSecurityToken>
  41. <wst:RequestedTokenReference>
  42. <wsse:KeyIdentifier ValueType="urn:passport"></wsse:KeyIdentifier>
  43. <wsse:Reference URI="#BinaryDAToken0"></wsse:Reference>
  44. </wst:RequestedTokenReference>
  45. <wst:RequestedProofToken>
  46. <wst:BinarySecret>ignore this one</wst:BinarySecret>
  47. </wst:RequestedProofToken>
  48. </wst:RequestSecurityTokenResponse>
  49. <wst:RequestSecurityTokenResponse>
  50. <wst:TokenType>urn:passport:compact</wst:TokenType>
  51. <wsp:AppliesTo xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
  52. <wsa:EndpointReference>
  53. <wsa:Address>messengerclear.live.com</wsa:Address>
  54. </wsa:EndpointReference>
  55. </wsp:AppliesTo>
  56. <wst:LifeTime>
  57. <wsu:Created>2006-12-06T05:12:10Z</wsu:Created>
  58. <wsu:Expires>2006-12-06T13:12:10Z</wsu:Expires>
  59. </wst:LifeTime>
  60. <wst:RequestedSecurityToken>
  61. <wsse:BinarySecurityToken Id="Compactn">
  62. t=<ticket goes here>&p=
  63. </wsse:BinarySecurityToken>
  64. </wst:RequestedSecurityToken>
  65. <wst:RequestedTokenReference>
  66. <wsse:KeyIdentifier ValueType="urn:passport:compact"></wsse:KeyIdentifier>
  67. <wsse:Reference URI="#Compactn"></wsse:Reference>
  68. </wst:RequestedTokenReference>
  69. <wst:RequestedProofToken>
  70. <wst:BinarySecret>binary secret (you need this)</wst:BinarySecret>
  71. </wst:RequestedProofToken>
  72. </wst:RequestSecurityTokenResponse>
  73. <wst:RequestSecurityTokenResponse>
  74. <wst:TokenType>urn:passport:legacy</wst:TokenType>
  75. <wsp:AppliesTo xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
  76. <wsa:EndpointReference>
  77. <wsa:Address>site domain</wsa:Address>
  78. </wsa:EndpointReference>
  79. </wsp:AppliesTo>
  80. <wst:LifeTime>
  81. <wsu:Created>2006-12-06T05:12:10Z</wsu:Created>
  82. <wsu:Expires>2006-12-06T05:20:30Z</wsu:Expires>
  83. </wst:LifeTime>
  84. <wst:RequestedSecurityToken>
  85. <wsse:BinarySecurityToken Id="PPTokenn">
  86. t=<site ticket here>&p=<site profile here>
  87. </wsse:BinarySecurityToken>
  88. </wst:RequestedSecurityToken>
  89. <wst:RequestedTokenReference>
  90. <wsse:KeyIdentifier ValueType="urn:passport"></wsse:KeyIdentifier>
  91. <wsse:Reference URI="#PPTokenn"></wsse:Reference>
  92. </wst:RequestedTokenReference>
  93. </wst:RequestSecurityTokenResponse>
  94. ...
  95. ...
  96. </wst:RequestSecurityTokenResponseCollection>
  97. </S:Body>
  98. </S:Envelope>

我们发送的XML中请求了几个认证的域,服务求就会返回对应的<wst:RequestSecurityTokenResponse>节点。其中<wsse:BinarySecurityToken Id="Compactn"> 和 RSTn 中的数字n对应,其节点值t就是对用的认证使用的ticket<wsse:BinarySecurityToken Id="PPTokenn">节点中包含ticket和profile,因为对用的服务需要使用到这两个值,目前我们在获取离线消息时需要使用。如果我们验证时的账户和密码不正确时,返回的XML会包含wsse:FailedAuthentication

至此我们已经完成了SSO认证的过程,我们可以把ticket 、profile保存下来,供后面使用,这里要注意,返回的XML中包含ticke的过期时间,如果过期了,必须从新进行请求。

.

2 登录验证

前面我们已经获得所有需要的ticket,当然登录需要的ticket也获得了。另外我们还需要从保存登录ticket节点下<wst:BinarySecret>节点中的值,我们叫做BinarySecret。以下是在完成SSO认证之后,进行的登录身份验证发送的命令。

<<<USR 3 SSO S MBI_KEY_OLD kVJy53UCBCupSu09gfE9rH47mtDGzmINva5/vEiYJIfxft+an0igQBC445kNyIkV/r/n

>>SSO认证---webservices

<<<获得各个服务的ticket

>>>USR 4 SSO S t=EwBoAswbAQAUs1/VcBU2sH7mwYy3BysWZ71CRDGAAP2ngxo5hiNLK0FzCGY1llPy8F5Uv8GQVTL2FDWo0UFZ2P4kDFk95WWhFl4ydSN8zJQVpzq5YlhSaTJc/JziNMZV0RBOaNDv0yuuGKPZ7gkaHLF5QDF5t0xChHLupla0+WYt5N3rnfRjU8QnYqgvkdMtEolkfInY0lxyyfmBSVeSA2YAAAhynJ+hLqBEy7gBZf6U97YpNCZdVsu8Uwr5GMs0FY8aWocMlSKU0V3rBP4igonwRPb/VLYGAztVZxFHZx/abNHOt/Sh83M7LWhqvPPbcWKuOIcevMzwParNhKZ2VWS1TymnQuqnA8igbYrDtAi1QcD6iEoQy/sM4cb7ryM/MjUZCbWQ6xg5CecLNlZT5onfqZ2IJJSZjrRO/9ZEU0rpH7N8nj/ycJVgiKJR8emWDec7eh1CbFltxtFz3ZCnWcwU+GCqhTU7IsTLrX6LYxjSTUm4eG8x19mxbJpjEv1Zpgqajdwfobu4FzKe0yF64hnVJd2Kv8hqnTq38XtcocSNexs/Sue4SknaOMG2u69Yu7b6CD/Ih+BOu4V2ZKn/EndfcW7tC7jh0x8qdTzy7sjJqQuz6sGMCr1uEt3Lx7tlT7nj+RDW5MG9MEMFid4ordfe2R1DV4bKB3D4xZWc7gT4rjfTcxtlRnrP6kOM6n9hPDScewrj4fknSzW1OKd8yCEtdQjb4pnvvmWRWqDO1ImQPLcK5Q3CqYb0BGf53w3fL/FEoWD3m0drktFlK3nGW4F3smsfdOkFGnYF/zSg7Jg9erW7Z8LlAQ==&p= HAAAAAEAAAADZgAABIAAAAgAAAAUAAAASAAAAAAAAAAAAAAAoHfdK2pjBY1wLTHXDzh9VtaqYOb2g4n+epmErFlCejBt2pMDFMNR4fFjknY6XsftT1qboX3K45VNK3ICTjEbPsvR1Sog3D14Rz2Q6JPKFrEAWZ23PEjCV6f8XJw= {95DC3D4B-7FBF-46B5-B670-FE0E75B89217}

<<<USR 4 OK test@live.cn 1 0

以上继续使用USR SSO S命令,最后一个参数的格式是t=...&p=...{epid} , 这里的t就是我们SSO中取得和登录有关的ticket,而p是由USR 3中那一长串nonce和上面取得的BinarySecret通过计算得到的,具体计算方法后面介绍,最后一个参数是客户端的ID,是一个GUID,用来唯一标识一个终端。如果重复,会导致之前登录的终端被踢下线。

发送此条命令之后,如果验证通过,服务器会发送USR OK通知客户端登录成果,如果发送的t和p不对,会返回错误代码:911 ERR_AUTHENTICATION_FAILED。

.

3 生成登录是发送的p

上面的USR 4命令中包含了p,这个p是由USR 3中得到的nonce和webservice请求获得的BinarySecret计算得到P。在介绍算法之前,我们需要一个结构体,我们最终的p就是这个结构体的值,发送给服务器。

  1. typedefstruct tagMSGRUSRKEY
  2. {
  3. /** 28. Does not count data */
  4. unsigned int uStructHeaderSize;
  5. /** CRYPT_MODE_CBC (1) */
  6. unsigned int uCryptMode;
  7. /** TripleDES (0x6603) */
  8. unsigned int uCipherType;
  9. /** SHA1 (0x8004) */
  10. unsigned int uHashType;
  11. /** 8 */
  12. unsigned int uIVLen;
  13. /** 20 */
  14. unsigned int uHashLen;
  15. /** 72 */
  16. unsigned int uCipherLen;
  17. /** IV Data */
  18. unsigned char aIVBytes[8];
  19. /** Hash Data */
  20. unsigned char aHashBytes[20];
  21. /** Cipher Date */
  22. unsigned char aCipherBytes[72];
  23. } MSGUSRKEY;

算法是按一下步骤来的:http://msnpiki.msnfanatic.com/index.php/MSNP15:SSO#Computing_the_return_value

1. 对获得的BinarySecret进行BASE64编码,存放到key1中

2.使用SHA_HMAC加密key1,按一下方法进行

hash1 = SHA1-HMAC(key1,"WS-SecureConversationSESSION KEY HASH")
hash2 = SHA1-HMAC(key1,hash1+"WS-SecureConversationSESSION KEY HASH")
hash3 = SHA1-HMAC(key1,hash1)
hash4 = SHA1-HMAC(key1,hash3+"WS-SecureConversationSESSION KEY HASH")
然后把hash的20个字节,以及hash的前4个字节组成一个24字节的字符串存入 key2.

3.使用SHA_HMAC加密key2,按一下方法进行

hash1 = SHA1-HMAC(key2,"WS-SecureConversationSESSION KEY ENCRYPTION")
hash2 = SHA1-HMAC(key2,hash1+"WS-SecureConversationSESSION KEY ENCRYPTION")
hash3 = SHA1-HMAC(key2,hash1)
hash4 = SHA1-HMAC(key2,hash3+"WS-SecureConversationSESSION KEY ENCRYPTION")
然后把hash的20个字节,以及hash的前4个字节组成一个24字节的字符串存入 key3。

4. 使用SHA_HMAC加密key2和前面得到的nonoc,存入hash = SHA1-HMAC(key2, nonce)

5. 官方客户端会在nonce的后面追加8个字节的16进制数08

6. 创建8个字节随机数data

7. 使用TripleDes algorithm算法。模式设置为CBC,将上面的key3,随机数data 以及 第5步产生的nonce进行加密,存入encrypted_data

8. 填充结构体

aIVBytes使用第6步创建的随机数填充,aHashBytes使用第4步的hash填充,aCipherBytes使用第7步计算的encrypted_data填充。而其他节点的值则按照结构体注释中的填写,如果不想使用默认值进行相应调整

9. 对结构体进行BASE64编码

以下是用C++代码实现的整个计算过程,我们使用openssl提供的SHA_HMAC,TripleDes algorithm以及BASE64方法。

[c-sharp] view plain copy print ?
  1. char* TCreateSSOP::GetSSOP(void)
  2. {
  3. MSGUSRKEY key;
  4. _NS_compute_usrkey(&key, (char*)m_szNonce.c_str(), (char*)m_szBinarySecret.c_str());
  5. return base64((unsignedchar*)&key,sizeof(MSGUSRKEY));
  6. }
  7. int TCreateSSOP::_NS_compute_usrkey(MSGUSRKEY *key,char *challenge,char *secret)
  8. {
  9. char *key1;
  10. char key2[24];
  11. char key3[24];
  12. int klen = 0;
  13. char *chg;
  14. DES_cblock iv = {0,0,0,0,0,0,0,0};
  15. DES_key_schedule sched1, sched2, sched3;
  16. const_DES_cblock dkey1, dkey2, dkey3;
  17. key->uStructHeaderSize = 28;
  18. key->uCryptMode = 1;
  19. key->uCipherType = 0x6603;
  20. key->uHashType = 0x8004;
  21. key->uIVLen = 8;
  22. key->uHashLen = 20;
  23. key->uCipherLen = 72;
  24. memset(key->aIVBytes, 0, 8);
  25. key1 = (char*)unbase64((unsignedchar*)secret, strlen(secret));
  26. klen = 24;
  27. _NS_compute_hash(key1, klen, "WS-SecureConversationSESSION KEY HASH", key2);
  28. _NS_compute_hash(key1, klen, "WS-SecureConversationSESSION KEY ENCRYPTION", key3);
  29. HMAC(EVP_sha1(), (unsigned char*)key2, klen, (unsignedchar*)challenge, strlen(challenge), key->aHashBytes, NULL);
  30. memcpy(dkey1, key3, 8);
  31. memcpy(dkey2, key3+8, 8);
  32. memcpy(dkey3, key3+16, 8);
  33. DES_set_key_unchecked(&dkey1, &sched1); // set the key schedule
  34. DES_set_key_unchecked(&dkey2, &sched2); // set the key schedule
  35. DES_set_key_unchecked(&dkey3, &sched3); // set the key schedule
  36. chg = (char*)malloc(strlen(challenge)+9);
  37. memcpy(chg, challenge, strlen(challenge));
  38. memset(chg+strlen(challenge), 8, 8);
  39. *(chg+strlen(challenge)+8) = 0;
  40. DES_ede3_cbc_encrypt((unsigned char*)chg, key->aCipherBytes, 72, &sched1, &sched2, &sched3, &iv, DES_ENCRYPT);
  41. free(chg);
  42. free(key1);
  43. return 0;
  44. }
  45. char* TCreateSSOP::_NS_compute_hash(char *key,int klen,constchar *magic,char *result)
  46. {
  47. int mlen = strlen(magic);
  48. staticchar ret[24];
  49. unsigned int mdlen1, mdlen2, mdlen3, mdlen4;
  50. unsigned char hash1[EVP_MAX_MD_SIZE];
  51. unsigned char hash2[EVP_MAX_MD_SIZE];
  52. unsigned char hash3[EVP_MAX_MD_SIZE];
  53. unsigned char hash4[EVP_MAX_MD_SIZE];
  54. unsigned char buf[EVP_MAX_MD_SIZE*2];
  55. HMAC(EVP_sha1(), (unsigned char*)key, klen, (unsignedchar*)magic, mlen, hash1, &mdlen1);
  56. memcpy(buf, hash1, mdlen1);
  57. memcpy(buf+mdlen1, magic, mlen);
  58. HMAC(EVP_sha1(), (unsigned char*)key, klen, buf, mlen+mdlen1, hash2, &mdlen2);
  59. HMAC(EVP_sha1(), (unsigned char*)key, klen, hash1, mdlen1, hash3, &mdlen3);
  60. memcpy(buf, hash3, mdlen3);
  61. memcpy(buf+mdlen3, magic, mlen);
  62. HMAC(EVP_sha1(), (unsigned char*)key, klen, buf, mdlen3+mlen, hash4, &mdlen4);
  63. memcpy(ret, hash2, 20);
  64. memcpy(ret+20, hash4, 4);
  65. memcpy(result,ret, 24);
  66. return ret;
  67. }
  68. char* base64(const unsignedchar *input,int length)
  69. {
  70. //如果没制定长度,则根据要加密字符串的长度
  71. if (length == 0)
  72. {
  73. length = strlen((constchar*)input);// 需要加密长度
  74. }
  75. //计算存放加密数据的长度
  76. int bufferLen = length/3*4 + (length%3 ?4 : 0) + 1;
  77. char *buffer = (char*)malloc(bufferLen);
  78. memset(buffer, 0, bufferLen);
  79. //base64加密
  80. EVP_EncodeBlock((unsigned char*)buffer, input, length);
  81. return buffer;
  82. }

.

.

三 小结

自此,我们成功的完成了从连接DS服务器到MSN的登录身份验证,注意,这里仅仅是完成身份验证,并不是完成了实际的登录过程。除此之外,我们还获得了和面获取联系人列表、离线消息,联系人头像等操作时,请求Webservice服务所需要的ticket。整个登录过程复杂的就在于构建SSO请求的XML,以及计算p值。

在登录过程中可能出现的错误就是账号或密码错误导致SSO认证失败,也可能是计算P值出错,导致登录验证失败。所以在程序中必须对这两种错误进行处理。

转自:http://blog.csdn.net/cc_net/article/details/5986215

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页