22

我正在尝试在 Android 应用程序中创建一个SSLSocket在另一个之上。SSLSocket下层连接是到安全 Web 代理(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:从概念上讲,这些是连接经过的步骤:

  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(通过 Socket)的 SSLSocket 上进行握手时。


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


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

4

5 回答 5

7

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

来自代理/隧道wireshark的分析:

JRE案例:

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

握手成功,连接完成

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

现在让我们看一下android客户端案例。 在此处输入图像描述

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

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

在此处输入图像描述

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

结论:

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

这个问题仍然没有解决,你可以跟进 android 开发团队来解决这个问题。

可能的解决方案:

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

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

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

看看你在这个调用中的协议:sslSocket.setEnabledProtocols(sslSocket.getSupportedProtocols());

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

于 2017-02-10T17:50:58.163 回答
0

我不知道这是否有帮助,但是:
我已经建立了你的 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);
    }
}
于 2017-02-16T21:10:47.807 回答
0

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

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

于 2020-09-21T14:42:25.130 回答
0

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

这是链接可能会有所帮助。

通过代理服务器的 HTTPS 连接

于 2017-02-16T07:20:55.807 回答