SSLSocket通过另一个SSLSocket

时间:2017-02-06 22:09:36

标签: java android ssl cryptography sslsocketfactory

我尝试在Android应用中的另一个SSLSocket上创建SSLSocket。较低的连接是与Secure Web Proxy(基于SSL的HTTP代理)的SSL加密连接,上层连接用于HTTP over SSL(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.   
  3. 与代理服务器(SSLSocket over Socket)进行SSL / TLS握手
  4.   
  5. 通过SSLSocket,将CONNECT消息发送到代理服务器
  6.   
  7. 代理连接到目标(https)服务器,仅从现在起复制字节
  8.   
  9. 与目标(https)服务器进行SSL / TLS握手(SSLSocket over SSocket over SSocket)
  10.   
  11. 将GET消息发送到目标服务器并阅读响应
  12.         

    当在SSLSocket上进行握手时,问题出现在步骤5中   它遍历SSLSocket(通过Socket)。

  

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

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

5 个答案:

答案 0 :(得分:7)

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

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

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

答案 1 :(得分:7)

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

从您提供的tcpdump中,可以获得大量信息,以了解Android如何使用与JRE相同的API集合。

让我们来看看JRE tcpdump:

enter image description here

  • 查看初始握手消息(Client Hello,Server Hello,更改密码规范)。这显示了JRE客户端和代理服务器之间的握手。这很成功。
  • 现在我们没有看到JRE客户端和www.google.com(终端服务器)之间的第二次握手,因为我们正在通过SSL进行SSL加密。代理服务器将它们一点一点地复制到终端服务器。所以这是一种正确的行为。

现在让我们来看看android tcpdump:

enter image description here

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

来自proxy / stunnel wireshark的分析:

JRE案例:

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

握手成功,连接完成

然后客户端尝试连接到远程服务器(www.google.com)并开始握手。因此客户端发送的客户端问候语被视为数据包#34中的加密消息,当stunnel对其进行解密时,可以在“客户端问候”中看到它,该信息由stunnel转发到代理服务器 enter image description here

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

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

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

enter image description here

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

<强>结论:

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

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

可能的解决方案:

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

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

答案 2 :(得分:0)

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

以下链接可能有所帮助。

HTTPS connections over proxy servers

答案 3 :(得分: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);
    }
}

答案 4 :(得分:0)

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

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