使用客户端证书和 Android 的 HttpsURLConnection 通过 SSL 上传文件
Posted
技术标签:
【中文标题】使用客户端证书和 Android 的 HttpsURLConnection 通过 SSL 上传文件【英文标题】:Uploading a file over SSL with Client Side Certificate and Android's HttpsURLConnection 【发布时间】:2014-10-10 14:58:20 【问题描述】:我正在尝试将文件上传到受 SSL 保护并需要客户端证书(由内部 CA 签名)的 Web 服务。 与 Web 服务的通信运行良好(下载文件、查询、运行命令和执行各种 POST 工作正常),上传文件除外。
上传文件时,我收到一个 SSLException (javax.net.ssl.SSLException),上面写着“写入错误:ssl=0x5fe209c0:系统调用期间的 I/O 错误,对等方重置连接”。
我创建了一个重复的服务器并删除了 SSL 和客户端证书要求,并尝试通过“vanilla”HTTP 上传,它运行良好。
我尝试使用 setFixedLengthStreamingMode(int) 和 setChunkedStreamingMode(int) 没有成功。使用它们时,从write
方法抛出异常,当不使用它们时,调用getResponseCode()
时抛出相同的异常。
我在服务器的EventVwr
中找不到任何有关该错误的信息。
我们的另一个客户端(ios 客户端)能够在那里上传文件,所以它一定是我做的——但我不知道是什么。
我不确定如何进一步调试此问题。
请帮忙。
编辑 1
我们做了很多调试工作,发现:
按预期上传小文件(44kb 是成功上传的最大文件的大小,上传时间约为 1200 毫秒)。 一个 46kb 的文件上传失败。失败耗时约 2 分钟(134120 毫秒)。编辑 2
在您在评论中看到的内容之后,现在我让 Fiddler 玩得很好(感谢 this question)。 Fiddler 得到了文件,但没有成功发送。 请求(原始)如下所示:
POST https://192.168.2.2/rest/transfer/strong/Upload/Full?Path=%5C20140807_113255_20.jpg&Root=2 HTTP/1.1
SessionToken: 1234 // We use this for session management
FileMetadata: "FileSize":"1315496","FileName":"GrumpyCat.jpg"
Connection: Keep-Alive
User-Agent: Dalvik/1.6.0 (Linux; U; android 4.1.1; GT-N7100 Build/JRO03C)
Host: 192.168.2.2
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
Content-Length: 1315496
;odiao;awriorijgoeijoeirj;oedfrvgerg... // The image
Fiddler 的回应(也是 RAW)是:
HTTP/1.1 504 Fiddler - Send Failure
Date: Wed, 20 Aug 2014 17:40:29 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Timestamp: 20:40:29.420
[Fiddler] ResendRequest() failed: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host. < An existing connection was forcibly closed by the remote host
此外,我们添加了 WCF 的“MessageLogging”和详细的“Tracing”。 MessageLogging 不显示消息的任何提示(可能在变成消息之前被丢弃),但跟踪显示:
现在,在你说“啊,这是服务器问题”之前,请记住,44kb 文件上传成功,我们的 iOS 应用也能够成功上传文件。
这是客户端获得的异常调用堆栈:
E/RestClientUploader(3196): javax.net.ssl.SSLException: Write error: ssl=0x5d94b8b0: I/O error during system call, Connection reset by peer
E/RestClientUploader(3196): at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_write(Native Method)
E/RestClientUploader(3196): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLOutputStream.write(OpenSSLSocketImpl.java:693)
E/RestClientUploader(3196): at java.io.ByteArrayOutputStream.writeTo(ByteArrayOutputStream.java:231)
E/RestClientUploader(3196): at libcore.net.http.ChunkedOutputStream.writeBufferedChunkToSocket(ChunkedOutputStream.java:129)
E/RestClientUploader(3196): at libcore.net.http.ChunkedOutputStream.write(ChunkedOutputStream.java:77)
E/RestClientUploader(3196): at java.io.DataOutputStream.write(DataOutputStream.java:98)
E/RestClientUploader(3196): at com.varonis.datanywhere.communication.RestClientUploader.uploadFileToServer(RestClientUploader.java:151)
E/RestClientUploader(3196): at com.varonis.datanywhere.communication.RestClientUploader.uploadFullFile(RestClientUploader.java:67)
E/RestClientUploader(3196): at com.varonis.datanywhere.communication.services.FileUploadService.doUpload(FileUploadService.java:128)
E/RestClientUploader(3196): at com.varonis.datanywhere.communication.services.FileUploadService.onHandleIntent(FileUploadService.java:98)
E/RestClientUploader(3196): at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
E/RestClientUploader(3196): at android.os.Handler.dispatchMessage(Handler.java:99)
E/RestClientUploader(3196): at android.os.Looper.loop(Looper.java:137)
E/RestClientUploader(3196): at android.os.HandlerThread.run(HandlerThread.java:60)
【问题讨论】:
这很奇怪。这是多部分上传吗? (可能是题外话)。也许服务器已经响应了您,但您仍在尝试使用相同的连接写入服务器。此时服务器已发回RST
并继续。您可以使用Charles 之类的 Web 调试代理来拦截和监控 HTTPS 流量(要求您在客户端接受 charles CA)。监控来自服务器的 HTTPS 响应并告诉我们它是什么样的。
@DeepakBala - 我试过了,但它没有工作。我已将客户端设置为接受所有证书(信任所有内容的自定义信任管理器),但是一旦将 Fiddler(与 charles AFAIK 相同)设置为代理,连接就会失败(在 checkOpen 期间显示“套接字已关闭”的 SocketException OpenSSLSocketImpl.java 的方法(在 startHandshake 中)(虽然我在 Fiddler 中看到了隧道)。
那么 Fiddler 报告的服务器流量是什么?当它代理你的请求时会发生什么,服务器给出的响应是什么?
@DeepakBala - Fiddler 很有趣。我设法让 Fiddler 显示 SSL 会话,但仅在不使用客户端证书的情况下(在这些情况下,上传按预期工作)。当使用客户端证书并引入 fiddler(并让我们的客户端信任所有证书)时,我可以在 Fiddler 中看到 2 个隧道会话,但是通信中断并且登录(上传的先决条件)失败,正如我在之前的评论中描述的那样.
这可能与这个问题有关:code.google.com/p/android/issues/detail?id=61706
【参考方案1】:
不是答案,更多的是变通方法,供您参考。
在对这个问题进行了猛烈抨击并进行了大量研究之后,我们放弃了。我们打开了this issue with Google,并实施了以下解决方法:
为了上传文件,应用程序首先通过需要客户端证书的端点获取 上传令牌,然后使用此令牌上传到不需要客户端证书的端点客户端证书(但仍通过 SSL (Https))。
是的,这是轻微的安全漏洞,但我们必须这样做。我们已尽我们所能保护它。
我保证会在 Google 的票证更新(并希望得到解决)时进行更新。
【讨论】:
提供的链接似乎需要一个帐户。您是否有能力通过任何新的发展来更新此内容?【参考方案2】:有点晚了(因为您已经实施了解决方法)但这应该可以解决问题:https://***.com/a/9224892/1619545
我们遇到了同样的问题,将 client cert协商 标志设置为 enabled 似乎是唯一有帮助的方法。在此处查看如何更改证书绑定标志的方法:
http://help.sap.com/saphelp_smp305svr/helpdata/en/6f/f0a9b6e1c743d48d1e57235d297c1c/content.htm
【讨论】:
以上是关于使用客户端证书和 Android 的 HttpsURLConnection 通过 SSL 上传文件的主要内容,如果未能解决你的问题,请参考以下文章
CA双向认证补充:java客户端使用优化及证书链和Android证书