使用客户端证书和 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证书

在 Android 设备上生成客户端证书

让我们加密证书与android api <20一起使用

获取 Letsencrypt 证书以使用 android api < 20

Android使用https与服务器交互的正确姿势

Android中的OCSP证书装订