通过另一个SSLSocket使用SSLSocket

22

我正在尝试在安卓应用程序中创建一个基于另一个SSLSocketSSLSocket。较低的连接是到安全 Web 代理(通过 SSL 的 HTTP 代理)的 SSL 安全连接,而较高的连接是用于 SSL 上的 HTTP(HTTPS)。

为此,我正在使用 SSLSocketFactory 的createSocket()函数,该函数允许传递现有的套接字以运行 SSL 连接,如下所示:

private Socket doSSLHandshake(Socket socket, String host, int port) throws IOException {
    TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager(){
                public X509Certificate[] getAcceptedIssuers(){ return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
    };

    try {
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCerts, new SecureRandom());
        SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(socket, host, port, true);
        sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());
        sslSocket.setEnableSessionCreation(true);
        sslSocket.startHandshake();
        return sslSocket;
    } catch (KeyManagementException | NoSuchAlgorithmException e) {
        throw new IOException("Could not do handshake: " + e);
    }
}

当底层套接字为普通的TCP套接字时,此代码正常工作。但当我使用上述代码创建的SSLSocket作为底层套接字时,握手失败并出现以下异常:

javax.net.ssl.SSLHandshakeException: Handshake failed
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429)
    at com.myapp.MyThreadClass.doSSLHandshake(MyThreadClass.java:148)
    at com.myapp.MyThreadClass.run(MyThreadClass.java:254)
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x7374d56e80: Failure in SSL library, usually a protocol error
    error:100000e3:SSL routines:OPENSSL_internal:UNKNOWN_ALERT_TYPE (external/boringssl/src/ssl/s3_pkt.c:618 0x738418ce7e:0x00000000)
    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
    ... 2 more

我在Android 7.1.1上进行测试,应用程序的目标SDK级别为23。

  • 我可能做错了什么?
  • 我该如何进一步调试此问题?
  • 有没有任何人有一个在最近的Android版本上使用另一个SSLSocket的工作示例?

非常感谢任何帮助!


更新:相同的代码在Mac上的JRE 1.8中可以运行,但在Android上不能。


更新2:从Android应用程序开始,与安全代理服务器(Socket)建立连接

  1. 与代理服务器(SSLSocket over Socket)进行SSL/TLS握手
  2. 通过SSLSocket向代理服务器发送CONNECT消息
  3. 代理连接到目标(https)服务器并仅复制字节
  4. 与目标(https)服务器(SSLSocket over SSLSocket over Socket)进行SSL/TLS握手
  5. 发送GET消息以获取响应

问题出现在第5步,当对一个在SSLSocket上的SSLSocket执行握手时。


更新3:我现在已经打开了一个GitHub repo,其中包含一个样本项目和tcpdumps:https://github.com/FD-/SSLviaSSL


注意:我已经发现并阅读了一个具有非常相似标题的问题,但不幸的是它没有包含太多有用的帮助。


1
你使用的代理是需要客户端和代理之间进行安全的SSL通信吗?这非常不寻常。通常,客户端会使用非安全连接与代理进行通信,只有在代理连接到目标服务器之后,客户端才会与目标服务器发起SSL握手。代理只是一个静默的中转站,加密只存在于客户端和目标服务器之间,而不是客户端和代理之间。 - Remy Lebeau
不,你不需要这样做。连接是以明文方式进行的,如果成功,客户端将开始与上游服务器进行SSL握手。从那时起,代理所要做的就是在两个方向上复制字节。除此之外,任何其他操作都会导致系统编写或配置不正确。HTTP代理操作已经定义得非常清楚了。 - user207421
1
@EJP 相信我需要这个。抽象出确切的用例:为什么 SSL over SSL 不能工作? - FD_
你能提供一个tcpdump吗? - manishg
@manishg 我现在已经创建了一个包含样本项目和 tcpdumps 的 GitHub 存储库。请查看存储库中的 tcpdump 目录。转储设置说明在自述文件中。 - FD_
显示剩余18条评论
5个回答

7

我尝试查找在Android情况下出了什么问题,但到目前为止,我没有发现您的代码有任何问题。由于该代码适用于JRE,它验证了这个假设。

根据您提供的tcpdump信息,可以得出结论:Android在与JRE相同的一组API上的行为。

让我们来看看JRE tcpdump:

enter image description here

  • 查看初始握手消息(客户端Hello,服务器Hello,更改密码规范)。这显示了JRE客户端和代理服务器之间的握手。这是成功的。
  • 现在我们看不到JRE客户端和www.google.com(终端服务器)之间的第二次握手,因为我们正在进行SSL over SSL加密。代理服务器按位复制它们到终端服务器。因此,这是正确的行为。

现在让我们看看android tcpdump:

enter image description here

  • 查看初始握手消息(客户端Hello,服务器Hello,更改密码规范)。这显示了Android客户端和代理服务器之间的握手。这是成功的。
  • 现在理想情况下,我们不应该看到第二次握手,因为它应该是加密的。但是在这里,我们可以看到Android客户端正在发送“客户端hello”,并将其发送到“www.google.com”,即使数据包被发送到代理服务器。 enter image description here
  • 上述操作注定会失败,因为数据包应该在SSL套接字上写入,而不是在初始明文套接字上写入。我审查了您的代码,并发现您正在通过SSLSocket进行第二次握手,而不是通过普通套接字进行。

代理/隧道Wireshark分析:

JRE案例:

在JRE的情况下,客户端与stunnel或代理服务器进行初始SSL握手。如下所示: enter image description here 握手成功并完成连接。
然后,客户端尝试连接到远程服务器(www.google.com)并开始握手。因此,由客户端发送的客户端hello在数据包#34中被视为加密消息,当stunnel解密时,它被视为“客户端hello”,然后由stunnel转发给代理服务器。 enter image description here 现在让我们看看Android客户端的情况。 enter image description here 从客户端到stunnel或代理的初始SSL握手成功,如上所示。
当Android客户端与远程服务器(www.google.com)握手时,最好使用SSL套接字。如果是这种情况,我们应该看到从Android到stunnel的加密流量(类似于JRE案例中的第34个数据包),stunnel应该解密并将“客户端hello”发送到代理。但是,如下所示,Android客户端正在通过普通套接字发送“客户端hello”。

enter image description here

如果您将JRE的数据包#24与数据包#34进行比较,我们可以发现这个区别。
结论:
这是与Android SSL(使用SSL套接字的factory.createsocket())实现有关的错误,我感觉可能没有使用相同API的神奇解决方法。事实上,我在Android错误列表中发现了这个问题。请参见下面的链接: https://code.google.com/p/android/issues/detail?id=204159 这个问题仍未解决,您可能需要跟进Android开发团队来解决此问题。
可能的解决方案:
如果我们得出结论,相同的API集不能工作,那么您只剩下一个选择:
1.编写自己的SSL包装器,覆盖SSL套接字。您可以手动进行握手或使用第三方实现。这可能需要一段时间,但似乎是唯一的方法。

感谢您详细的回答!您是否可能找出SSLFactory或SSLSocketImpl中导致问题的具体代码?Android使用的conscrypt提供程序的代码似乎在这里:https://android.googlesource.com/platform/external/conscrypt/ - FD_
我不知道具体是哪段代码引起的问题,但我怀疑像这样的提交(https://github.com/android/platform_frameworks_base/commit/725a4a71b8f2a5493628d87556c78860f66d2308)会导致它。 - manishg
@manishg 那些是你自己的截图,对吗?从FD_提供的截图来看,我发现有一些不同。所描述的设置使得从stunnel到squid的数据包在本地没有加密,因此可以看到发生了什么情况。然而,当客户端发送第二个“client hello”时(与您描述的相似),从帧24开始,事情似乎就出现了问题,除了我看不到服务器扩展之外。紧接着就是一个加密的警报,表明失败了。所以类似的问题是Android尝试进行第二个连接未加密,但是连接的是代理。 - Hod
1
是的,这些是我从设备/JRE中捕获的内容。即使是从FD_捕获中,我们也可以看到相同的东西。在Android情况下,我们可以在wireshark捕获中看到来自Android客户端的2个“客户端hello”消息。而在JRE情况下,只有一个客户端hello来自客户端。另一个“客户端hello”是stunnel发送到代理的内容。这证明了stunnel接收到加密的客户端hello,并在解密后将客户端hello转发给代理。我会编辑我的答案,以补充这些细节。 - manishg

7
我觉得您没有做错任何事情。看起来在第二次握手期间,协议协商出现了问题。一个很可能的原因是NPN TLS握手扩展失败。
请查看此调用中的协议:sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols()); 您可以逐个遍历列出的协议,并分别尝试它们。看看是否可以确定哪个协议或扩展程序存在问题,以及您是否需要支持特定的协议或扩展。

我可能有机会看一下。你能否建立一个 Stack Overflow 聊天室以便继续交流? - Hod
谢谢!我在这里创建了一个带有示例项目的 GitHub 存储库: https://github.com/FD-/SSLviaSSL。聊天室在这里:http://chat.stackoverflow.com/rooms/info/135519/discussion-on-sslsocket-via-another-sslsocket?tab=general - FD_
我可以通过安全的Web代理使用HttpsURLConnection吗?我认为这是不可能的。 - FD_
还没有尝试过,但是看看这篇文章:https://dev59.com/mGUo5IYBdhLWcg3wmASi 你需要设置 https.proxyHost 和 https.proxyPort(而不是 http)。这些信息也许会有所帮助:https://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html 另外,使用 System.setProperty("javax.net.debug", "all"); 开启完整调试。 - Hod
在聊天中添加了一些注释。 - Hod
显示剩余4条评论

0

由于HTTPS确保没有中间人干扰两者之间的通信,所以您无法这样做。因此第二次握手失败。

这里是可能有帮助的链接。

代理服务器上的HTTPS连接


似乎你没有理解我的代码的作用。我正在使用http CONNECT消息,它只是建立一个连接,然后在客户端和目标之间进行复制。 - FD_

0

事实证明,在我测试的任何安卓设备上,这确实是默认SSL提供程序中的一个错误(尽管我的最新设备是运行Android 7.1.1的Nexus 9)。 最终,我发现Conscrypt SSL提供程序独立版本的基于引擎的SSLSocket实现(当时刚发布)在安卓上按照我预期的方式工作。

如需进一步了解详情,请查看与GitHub上Conscrypt维护者的讨论: https://github.com/google/conscrypt/issues/104


0

我不知道这是否有帮助,但是:
我已经建立了您的存储库测试环境,我的错误略有不同:

I/SurfaceTextureClient(20733): [0x52851b98] frames:2, duration:1.005000, fps:1.989805
I/System.out(20733): [socket][2] connection /192.168.1.100:10443;LocalPort=35380(0)
I/System.out(20733): [CDS]connect[/192.168.1.100:10443] tm:90
I/System.out(20733): [socket][/192.168.1.123:35380] connected
I/System.out(20733): Doing SSL handshake with 192.168.1.100:10443
I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2]
E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback x509_store_ctx=0x542e0a80 arg=0x0
E/NativeCrypto(20733): ssl=0x53c96268 cert_verify_callback calling verifyCertificateChain authMethod=RSA
I/System.out(20733): Doing SSL handshake with 192.168.1.100:443
I/System.out(20733): Supported protocols are: [SSLv3, TLSv1, TLSv1.1, TLSv1.2]
E/NativeCrypto(20733): Unknown error during handshake
I/System.out(20733): Shutdown rx/tx
I/System.out(20733): [CDS]close[35380]
I/System.out(20733): close [socket][/0.0.0.0:35380]
W/System.err(20733): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException:
                        SSL handshake aborted: ssl=0x53c9c1d8:
                        Failure in SSL library, usually a protocol error
W/System.err(20733): error:140770FC:SSL routines:
                        SSL23_GET_SERVER_HELLO:
                        unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000)
W/System.err(20733):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:413)
W/System.err(20733):    at com.bugreport.sslviassl.SecureWebProxyThread.doSSLHandshake(SecureWebProxyThread.java:147)
W/System.err(20733):    at com.bugreport.sslviassl.SecureWebProxyThread.run(SecureWebProxyThread.java:216)
W/System.err(20733): Caused by: javax.net.ssl.SSLProtocolException:
                        SSL handshake aborted: ssl=0x53c9c1d8:
                        Failure in SSL library, usually a protocol error
W/System.err(20733): error:140770FC:SSL routines:
    SSL23_GET_SERVER_HELLO:unknown protocol (external/openssl/ssl/s23_clnt.c:766 0x4e7cb3ad:0x00000000)
W/System.err(20733):    at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
W/System.err(20733):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:372)
W/System.err(20733):    ... 2 more
I/SurfaceTextureClient(20733): [0x52851b98] frames:5, duration:1.010000, fps:4.946089

关于线程的一个想法:你在哪个线程上运行doSSLHandshake()方法?

public void policy()
{
    int SDK_INT = android.os.Build.VERSION.SDK_INT;
    if (SDK_INT > 8)
    {
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
    }
}

我认为在Android 4.1上运行应用程序时遇到了这个异常。我猜测错误信息的差异源于不同的SSL提供商在Android版本之间发生了变化。 - FD_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接