SSLSocket 通过另一个 SSLSocket

Posted

技术标签:

【中文标题】SSLSocket 通过另一个 SSLSocket【英文标题】:SSLSocket via another SSLSocket 【发布时间】:2017-06-24 00:11:30 【问题描述】:

我正在尝试在 android 应用中在另一个 SSLSocket 之上创建一个 SSLSocket。下层连接是到Secure Web Proxy(基于 SSL 的 HTTP 代理)的 SSL 安全连接,上层连接是基于 SSL 的 HTTP (HTTPS)。

为此,我正在使用 SSLSocketFactory 的 createSocket() 函数,该函数允许传递一个现有的 Socket 来运行 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。

我可能做错了什么? 如何进一步调试问题? 有没有人有一个 SSLSocket 超越另一个的工作示例 最新 Android 版本上的 SSLSocket?

非常感谢任何帮助!


更新:完全相同的代码在 Mac 上的 JRE 1.8 中有效,但在 Android 上无效。


更新 2:从概念上讲,这些是连接所经历的步骤:

    从 Android 应用程序连接到安全代理服务器(套接字) 与代理服务器进行 SSL/TLS 握手(SSLSocket over Socket) 通过 SSLSocket 向代理服务器发送 CONNECT 消息 代理连接到目标 (https) 服务器,从现在开始只复制字节 与目标 (https) 服务器进行 SSL/TLS 握手(SSLSocket over SSLSocket over Socket) 将 GET 消息发送到目标服务器并读取响应

问题出现在第 5 步,在 SSLSocket 上进行握手时 通过 SSLSocket(通过 Socket)。


更新 3:我现在打开了一个包含示例项目和 tcpdump 的 GitHub 存储库:https://github.com/FD-/SSLviaSSL


注意:我已经找到并阅读了a question with very similar title,但遗憾的是它并没有包含太多有用的帮助。

【问题讨论】:

你误会了。您不需要外部插座。代理将向目标使用 SSL。 您使用的是什么代理,需要客户端和代理之间的通信才能通过 SSL 确保安全?这是非常不寻常的。通常,客户端会使用不安全的连接与代理进行通信,并且只有在代理连接到目标服务器后,客户端才会启动与目标的 SSL 握手。代理只是一种静默传递,唯一的加密是在客户端和目标之间,而不是在客户端和代理之间。 @RemyLebeau 这可能不寻常,但我的设置需要通过 SSL 连接与代理通信。 您可能知道如何按照链接问题的解决方案中的建议使用不同的 SSL 提供程序吗? @EJP 当我说我确实需要这个时请相信我。抽象出确切的用例:为什么 SSL 上的 SSL 不起作用? 【参考方案1】:

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

更多详情,请查看 GitHub 上与 Conscrypt 维护者的讨论:https://github.com/google/conscrypt/issues/104

【讨论】:

【参考方案2】:

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

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 版本之间发生了变化。【参考方案3】:

由于 HTTPS 确保没有中间人中断两者之间的通信。这就是为什么你不能这样做。所以第二次握手失败了。

此链接可能会有所帮助。

HTTPS connections over proxy servers

【讨论】:

您似乎没有理解我的代码的作用。我正在使用 http CONNECT 消息,它只是建立一个连接,然后在客户端和目标之间进行复制。【参考方案4】:

所以我试图找出在 android 的情况下出了什么问题,但到目前为止我没有发现你的代码有任何问题。此外,由于代码适用于 JRE,因此它断言了假设。

从您提供的 tcpdump 中,有大量信息可以推断出 Android 如何使用与 JRE 相同的 API 集。

我们来看看 JRE tcpdump:

查看初始握手消息(客户端问候、服务器问候、更改密码规范)。这显示了 JRE 客户端和代理服务器之间的握手。这是成功的。 现在我们看不到 JRE 客户端和 www.google.com(终端服务器)之间的第二次握手,因为我们在 SSL 上执行 SSL 时已加密。代理服务器正在将它们一点一点地复制到最终服务器。所以这是一个正确的行为。

现在让我们看看android tcpdump:

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

来自代理/stunnel wireshark 的分析:

JRE 案例:

在 JRE 的情况下,客户端与 stunnel/代理服务器进行初始 SSL 握手。同样可以在下面看到:

握手成功,连接完成

然后客户端尝试连接到远程服务器 (www.google.com) 并开始握手。所以客户端发送的客户端问候被视为数据包#34中的加密消息,当stunnel解密时,它会在“客户端问候”处看到,由stunnel转发到代理服务器

现在让我们看看android客户端案例。

从客户端到 stunnel/proxy 的初始 SSL 握手成功,如上所示。

然后当 android 客户端开始与远程 (www.google.com) 握手时,理想情况下它应该使用 SSL 套接字。如果是这种情况,我们应该看到从 android 到 stunnel 的加密流量(类似于 JRE 案例中的数据包 #34),stunnel 应该解密并将“client hello”发送到代理。但是正如您在下面看到的,android 客户端正在通过普通套接字发送“客户端问候”。

如果您将数据包 #24 与来自 JRE 的数据包 #34 进行比较,我们可以发现这种差异。

结论:

这是 android SSL (factory.createsocket() with SSL socket) 实现的一个错误,我觉得使用相同的 API 集可能没有神奇的解决方法。事实上,我在 android bug list 中发现了这个问题。见以下链接: https://code.google.com/p/android/issues/detail?id=204159

此问题仍未解决,您可能可以跟进 android 开发团队来解决此问题。

可能的解决方案:

如果我们断定同一组 API 无法工作,那么您就只有一个选择:

    在 SSL 套接字上编写您自己的 SSL 包装器。你可以手动做 握手或使用第三方实现。这可能需要一个 虽然但看起来是唯一的方法。

【讨论】:

感谢您的详细解答!您是否可能发现 SSLFactory 或 SSLSocketImpl 中的哪些特定代码导致了问题? Android使用的conscrypt provider的代码好像在这里:android.googlesource.com/platform/external/conscrypt 我不知道会导致哪个特定代码,但我怀疑像这样的一些提交 (github.com/android/platform_frameworks_base/commit/…) 会导致它。 @manishg 这些是你自己的捕获,对吧?查看 FD_ 提供的捕获,我发现有些不同。所描述的设置在本地主机上清楚地显示了从 stunnel 到 squid 的数据包,因此您可以看到发生了什么。正如您所描述的,当客户端发送第二个“客户端问候”时,事情似乎从第 24 帧开始分崩离析,除了我没有看到服务器扩展。紧随其后的是加密警报,指示失败。如此相似的问题,Android 会尝试第二个未加密的连接,但要连接到代理。 是的,这些是我从设备/JRE 捕获的。即使从 FD_ 捕获中,我们也看到了同样的情况。在 android 案例中,我们可以在来自 android 客户端的 wireshark 捕获中看到 2 条“client hello”消息。而在 JRE 情况下,只有一个来自客户端的客户端问候。另一个“客户你好”是 stunnel 发送到代理的东西。这证明了 stunnel 收到了一个加密的客户端 hello 并在解密后将客户端 hello 转发给代理。我将在我的答案中添加一个编辑以添加这些详细信息。【参考方案5】:

我不认为你做错了什么。在您的第二次握手期间,协议协商中似乎存在错误。一个好的候选人会在 NPN TLS 握手扩展中失败。

在此通话中查看您的协议:sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

您可以浏览列出的协议并逐个尝试。看看您是否可以锁定失败的原因以及您是否需要支持该特定协议或扩展。

【讨论】:

感谢您的回答!我刚刚尝试了 [SSLv3, TLSv1, TLSv1.1, TLSv1.2] 的每个协议(支持的协议),但不幸的是,所有 tls 版本都产生了错误(而且我的服务器似乎不支持 SSL)。 您对 SSL/TLS 有一些背景知识吗?您介意看一个最小的完整可验证示例项目吗? 我可能有机会看看。您可以设置一个 SO 聊天以继续吗? 谢谢!我在这里创建了一个带有示例项目的 GitHub 存储库:github.com/FD-/SSLviaSSL。聊天在这里:chat.***.com/rooms/info/135519/… 尝试用 javax.net.ssl.HttpsURLConnection 代替 SSLSocket。还没有机会测试它,但谷歌信息表明它可能会得到更多更新。

以上是关于SSLSocket 通过另一个 SSLSocket的主要内容,如果未能解决你的问题,请参考以下文章

使用 SSLSocket 的 SOCKS5 代理

不是SSLSocket getInputStream()的结果。

SSLSocket

[Unsupported ciphersuite][Java SSLSocket]

Https 代理 sslsocket

尝试发送到 SSLServerSocket 时出现 SSLSocket 异常握手错误