SSLSocket via another SSLSocket

我正在尝试在Android应用程序中创建另一个之上。下部连接是与安全 Web 代理(基于 SSL 的 HTTP 代理)的 SSL 安全连接,上部连接用于 HTTP over SSL (HTTPS)。SSLSocketSSLSocket

为此,我正在使用SSSOCKetFactory的函数,该函数允许传递一个现有的Socket来运行SSL连接,如下所示:createSocket()

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:从概念上讲,这些是连接所经历的步骤:

  1. 从 Android 应用中,连接到安全代理服务器(套接字)
  2. 与代理服务器进行 SSL/TLS 握手(SSLSocket over Socket)
  3. 通过 SSLSocket,将 CONNECT 消息发送到代理服务器
  4. 代理连接到目标 (https) 服务器,从现在开始仅复制字节
  5. 与目标 (https) 服务器执行 SSL/TLS 握手(SSLSocket over SSLSocket over Socket)
  6. 将 GET 消息发送到目标服务器并读取响应

问题出现在步骤 5 中,当在经过 SSLSocket(通过套接字)的 SSLSocket 上进行握手时。


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


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


答案 1

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

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

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


答案 2

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

从您提供的 tcpdump 中,有大量信息可以得出 Android 在与 JRE 相同的 API 集下的行为。

让我们来看看 JRE tcpdump:

enter image description here

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

现在让我们来看看 android tcpdump:

enter image description here

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

来自代理/隧道线鲨的分析:

案例:

在 JRE 的情况下,客户端与 stunnel/proxy 服务器进行初始 SSL 握手。在下面可以看到相同的情况:enter image description here

握手成功,连接完成

然后,客户端尝试连接到远程服务器(www.google.com)并开始握手。因此,客户端发送的客户端 hello 在数据包 #34 中被视为加密消息,当 stunnel 解密相同的消息时,它会在“Client hello”处看到,该消息通过 stunnel 转发到代理服务器enter image description here

现在让我们来看看安卓客户端案例。enter image description here

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

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

enter image description here

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

结论:

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

这个问题仍未解决,您可以跟进Android开发团队以修复相同的问题。

可能的解决方案:

如果我们得出结论,同一组API无法工作,那么您只有一个选项:

  1. 在 SSL 套接字上编写自己的 SSL 包装器。您可以手动进行握手或使用第三方实现。这可能需要一段时间,但看起来是唯一的方法。