通过另一个 SSLSocket 的 SSLSocket

SSLSocket via another SSLSocket

我正在尝试在 Android 应用程序中的另一个 SSLSocket 之上创建一个 SSLSocket。下面的连接是到 Secure Web Proxy(基于 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。

非常感谢任何帮助!


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

上无效

Update 2: Conceptually, these are the steps the connection goes through:

  1. From the Android app, make a connection to the Secure Proxy server (Socket)
  2. Do an SSL/TLS handshake with the Proxy server (SSLSocket over Socket)
  3. Via the SSLSocket, send the CONNECT message to the Proxy server
  4. Proxy connects to destination (https) server and only copies bytes from now on
  5. Do an SSL/TLS handshake with the destination (https) server (SSLSocket over SSLSocket over Socket)
  6. Send the GET message to the destination server and read response

The problem arises in step 5, when doing the handshake on an SSLSocket that goes over an SSLSocket (that goes over a Socket).


Update 3: I have now opened a GitHub repo with a sample project and tcpdumps: https://github.com/FD-/SSLviaSSL


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

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

看看你在这次通话中的协议:sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

您可以浏览列出的协议并单独尝试它们。查看您是否可以锁定失败的内容以及您是否需要支持该特定协议或扩展。

所以我试图找出在 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 而不是普通套接字进行第二次握手。

来自proxy/stunnelwireshark的分析:

JRE 案例:

对于 JRE,客户端与 stunnel/proxy 服务器进行初始 SSL 握手。同样可以在下面看到:

握手成功,连接完成

然后客户端尝试连接到远程服务器 (www.google.com) 并开始握手。因此,客户端发送的客户端问候被视为数据包 #34 中的加密消息,当 stunnel 对其进行解密时,它在 "Client hello" 处被看到,由 stunnel 转发给代理服务器

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

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

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

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

结论:

这是 android SSL(factory.createsocket() 使用 SSL 套接字)实现的一个错误,我认为使用同一组 API 可能没有神奇的解决方法。事实上,我在 android 错误列表中发现了这个问题。见下文link: https://code.google.com/p/android/issues/detail?id=204159

此问题仍未解决,您可能 follow-up 与 android 开发团队一起解决此问题。

可能的解决方案:

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

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

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

这里 link 可能会有所帮助。

我不知道这是否有帮助但是:
我搭建了你的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 设备上默认 SSL 提供程序中的一个错误(我的最新设备是 Nexus 9 运行 Android 7.1.1 虽然). 最终,我发现 Conscrypt SSL 提供程序的独立版本的 engine-based SSLSocket 实现(当时刚刚发布)在 Android 上的工作方式完全符合我的预期。

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