为什么.Net CF在调用HTTPS 的Web服务时失败?!
开白:
今天,随便翻阅了一下.net framework compact小组的Blog,发现一篇非常有用的博文,作者是:Andarno,
原文地址是:
下面,小弟就按照自己的理解和词典的解释,把文章翻译过来,和大家一起分享一下关于这个问题的产生原因和解决经验(来自Andarno)吧。
正文:
在.Net CF 2.0(SP2或更早版本)和3.5版本中发现了一个Bug,它主要造成了程序使用HttpWebRequest方法进行HTTPS服务请求失败时,返回的一个提示:(对于Web服务)
System.Net.WebException: Unable to read data from the transport connection. ---> System.Net.Sockets.SocketException: Unknown error (0x0).
at System.Net.HttpWebRequest.fillBuffer(HttpWebRequest request, Connection connection, CoreResponseData data)
at System.Net.HttpWebRequest.getLine(HttpWebRequest request, Connection connection, CoreResponseData data)
at System.Net.HttpWebRequest.parseResponse(HttpWebRequest request, Connection connection, Boolean defaultKeepAlive)
at System.Net.HttpWebRequest.startReceiving(Connection connection)
at System.Net.Connection.startReceiving(Object ignored)
at System.Threading.ThreadPool.WorkItem.doWork(Object o)
at System.Threading.Timer.ring()
at System.Net.HttpWebRequest.finishGetResponse()
at System.Net.HttpWebRequest.GetResponse()
// ...
at Program.Main(String[] args)
虽然异常提示的内容以及产生原因是相同的,但是从你的程序中进行标准HttpWebRequest调用时,Stack trace信息将是不同的。
然而,产生这个异常的原因有很多,其中一个原因就是服务端发送了一个空的加密数据包给.NetCF客户端。下面是一个在HTTPS服务端加密的过程图示:
首先,服务端会初始化一个将通过网络要发送到客户端的、未加密的数据包,并存放到Memory Buffer中。
Step 1: |
| 未加密的数据 |
|
其次,服务端调用EncryptMessage方法,在适当的位置为这个未加密的数据包进行加密。主要是给这个数据包加入了一个Header和Footer:
Step 2: | header | 加密后的数据 | footer |
这个Header的长度可能是5bytes,那么Footer则要更长一些。对于这个数据包,它的长度肯定已经大于未加密的数据长度,在接下来的时候,它将被发送到接收端,并被执行DecryptMessage方法进行解密。
现在,当服务端把一个0长度的流加密成一个数据包发送到客户端时,.NetCF的SSL堆栈就产生问题了。
Step 1: |
| 0 byte的未加密数据 |
|
Step 2: | header | 非0 byte的加密后数据 | footer |
显然,加密前后,数据包长度已经发生变化。当这个数据包被发送到.NetCF客户端时,当前版本(译者按:2.0/3.5)的.NetCF将会解密这个数据包,并且返回一个0长度的数据流给调用者(译者按:我认为是上文中的Server端,下文提到的调用者也应该是Server端)。
通常意义上,对于一个网络中的Read方法而言(译者按:网络通信应该是互相往来,就如上文所提到的,服务端发送一个数据给客户端,那么客户端按照一定的规则返回一个结果到服务端,服务端使用Read方法进行接收),只有在收到数据后才执行,否则一直处于阻塞状态,如果收到的数据是一个0长度的数据流,那么则意味着这个socket连接已经被关闭。
因为.NetCF在解密收到的数据包后,返回给服务端的是一个空数据流(译者按:为什么要像这样返回呢,我人为这就是文章开始提及的bug了吧,貌似我们很难从根本上解决),调用者会将这个返回的空数据流误认为是一个socket连接断开的信号,所以它也就终止了这一次和客户端的连接。
事实上,这就是在.NetCF程序中通过SSL调用Web Service时,收到空数据包响应的整个过程。结果就是在服务器响应前,连接就失败了,并且抛出一个异常信息。
那么,是什么导致了服务端传送这样的空加密数据包呢?技术上来说,是由于这些空数据包被允许进行传送(虽然它们毫无意义),所以,这都看你的服务端如何配置,以确保这样的情况不会发生或极少发生。
但不幸的是,现在还没有办法让.NetCF去忽略这样的空数据包(译者按:我认为这里的.NetCF是指客户端程序),所以,只有用如下的变通方法来解决了,从简单到复杂:
1、 不使用SSL进行请求(由于请求和响应采用明文,会产生安全隐患)
2、 重新配置现有的服务端程序,让其避免产生空加密数据包。
3、 Build a new web server that will forward your device's requests on a separate connection to the ultimate target server and forward the responses back to the device. This would work for web sites and web services. But this new front-end server (which serves something of the role of a proxy server) would have to be configured to not generate these empty encryption packets.
4、 [Added 1/2/08] Build a device-side web proxy that calls into the SSPI functions itself (native would probably be easier than managed for this). Then have your managed app call into the proxy. This proxy would be responsible for consuming the empty packets and re-encrypting everything (if necessary to secure IPC) for the app on the same device without any empty SSL packets.
5、 Wait until a future version of NetCF that fixes this bug.
6、 Write your own HTTPS client using native code or P/Invoke'ing from NetCF (difficult to get right).
翻译完!
哈哈,没想到吧,后几条Jack还是留给大家理解一下吧,感觉英语来的更直接!感觉时不时看看这些blogs很有帮助,在这里还是感谢Andarno为我们做出的贡献,虽然这是很早之前的问题了,希望这篇文章对大家有用。我会继续努力地。
很久没有看E文了,这篇文章其实光看、理解,只用了10多分钟,但是翻译却花了30多分钟,感觉很多时候,英文很直接、很明白,往往到了中文这里却卡到阴。我尽量把这个原因归咎于自己的中文词汇和专业知识不够吧。嘿嘿……