简体   繁体   English

如何通过 HTTP 代理连接 SSL 套接字?

[英]How to connect a SSL socket through a HTTP proxy?

I'm trying to use Java (Android) to connect to a server with a SSL socket.我正在尝试使用 Java (Android) 连接到带有 SSL 套接字的服务器。 Please note that this is not HTTP data.请注意,这不是 HTTP 数据。 This is proprietary protocol with a mix of text and binary data.这是混合了文本和二进制数据的专有协议。

I want to relay that SSL connection through a HTTP proxy, but I am facing a lot of problems with that.我想通过 HTTP 代理中继该 SSL 连接,但我面临很多问题。 Right now the scenario that I use and that my browser seems to use with a squid proxy is as follow现在我使用的场景和我的浏览器似乎与鱿鱼代理一起使用的场景如下

[client]->[http connection]->[proxy]->[ssl connection]->[server] [客户端]->[http 连接]->[代理]->[ssl 连接]->[服务器]

This works for the browser, because after the proxy makes the ssl connection, a TLS negotiation takes place immediately.这适用于浏览器,因为在代理建立 ssl 连接后,TLS 协商立即发生。 However my code does not seem to do that.但是我的代码似乎没有这样做。

final TrustManager[] trustManager = new TrustManager[] { new MyX509TrustManager() };
final SSLContext context = SSLContext.getInstance("TLS");
context.init(null, trustManager, null);
SSLSocketFactory factory = context.getSocketFactory();
Socket s = factory.createSocket(new Socket(proxy_ip, 3128), hostName, port, true);

The problem that I have is that createSocket NEVER RETURNS.我遇到的问题是 createSocket 永远不会返回。 With a wireshark dump from the proxy machine, I can see that a tcp handshake takes place between the proxy and the server.使用来自代理机器的 wireshark 转储,我可以看到代理和服务器之间发生了 tcp 握手。 With dumps from web sessions, I can see that the client usually initiate a SSL handshake at this point, which does not happen in my scenario.通过 Web 会话的转储,我可以看到客户端通常会在此时启动 SSL 握手,这在我的场景中不会发生。

This is not a problem with the trust manager, because the certificate never gets back to me and it is never validated.这不是信任管理器的问题,因为证书永远不会返回给我,也永远不会被验证。

EDIT :编辑 :

After discussion, this is the more complete version of the code I'm trying to run.经过讨论,这是我尝试运行的代码的更完整版本。 This version above with the simple (new Socket(...)) as parameter is something I've tried later on.上面这个以简单的 (new Socket(...)) 作为参数的版本是我后来尝试过的。

The original version of the code I'm trying to debug throws我试图调试的代码的原始版本抛出
java.net.ConnectException: failed to connect to /192.168.1.100 (port 443): connect failed: ETIMEDOUT (Connection timed out)

The sequence is as follow (a bit simplified again) :顺序如下(再次简化了一点):

final Socket proxySocket = new Socket();
proxySocket.connect(proxyAddress, 2000); // 2 seconds as the connection timeout for connecting to the proxy server 
[Start a thread and write to outputStream=socket.getOutputStream()]
final String proxyRequest = String.format("CONNECT %s:%d HTTP/1.1\r\nProxy-Connection: keep-alive\r\nConnection: keep-alive\r\nHost: %s:%d\r\n\r\n", hostName, port, hostName, port);
outputStream.close(); // Closing or not doesn't change anything
[Stop using that thread and let it exit by reaching the end of its main function]

Then read the response with the following code :然后使用以下代码读取响应:

    final InputStreamReader isr = new InputStreamReader(proxySocket.getInputStream());
    final BufferedReader br = new BufferedReader(isr);
    final String statusLine = br.readLine();

    boolean proxyConnectSuccess = false;
    // readLine consumed the CRLF
    final Pattern statusLinePattern = Pattern.compile("^HTTP/\\d+\\.\\d+ (\\d\\d\\d) .*");
    final Matcher statusLineMatcher = statusLinePattern.matcher(statusLine);
    if (statusLineMatcher.matches())
    {
        final String statusCode = statusLineMatcher.group(1);
        if (null != statusCode && 0 < statusCode.length() && '2' == statusCode.charAt(0))
        {
            proxyConnectSuccess = true;
        }
    }

    // Consume rest of proxy response
    String line;
    while ( "".equals((line = br.readLine())) == false )
    {
    }

I can say that this code works because it works without SSL.我可以说这段代码有效,因为它在没有 SSL 的情况下有效。 The socket created here, proxySocket is the one that is passed to the createSocket function instead of just creating a new one on the fly like in my original example.此处创建的套接字proxySocket是传递给 createSocket 函数的套接字,而不是像我原来的示例中那样动态地创建一个新套接字。

java.net.Proxy , or the https.proxyHost/proxyPort properties, only support HTTP proxying via HttpURLConnection, not via a Socket. java.net.Proxyhttps.proxyHost/proxyPort属性仅支持通过HttpURLConnection,而不是通过Socket. HTTP 代理Socket.

To make that work for an SSLSocket of your own, all you need to to is create a plaintext socket, issue an HTTP CONNECT command on it, check the response for 200, and then wrap it in an SSLSocket.要使其适用于您自己的SSLSocket ,您只需创建一个纯文本套接字,对其发出HTTP CONNECT命令,检查 200 的响应,然后将其包装在SSLSocket.

EDIT When sending the CONNECT command, you must not close the socket, of course; EDIT发送 CONNECT 命令时,当然不能关闭套接字; and when reading its reply you must not use a BufferedReader, otherwise you will lose data;并且在阅读它的回复时你不能使用BufferedReader,否则你会丢失数据; either read the line by hand or use DataInputStream.readLine(), despite its deprecation.手动读取该行或使用DataInputStream.readLine(),尽管它已弃用。 You also need to follow RFC 2616 entirely.您还需要完全遵循 RFC 2616。

You have to use javax.net lib .您必须使用javax.net lib you can archive to your target using javax.net.ssl.* .您可以使用javax.net.ssl.*存档到您的目标。

I think you can get solution using oracle docs.我认为您可以使用 oracle docs 获得解决方案。 Here is the link for that.这是链接。

SSLSocketClientWithTunneling SSLSocketClientWithTunneling

Combine MacDaddy's answer and Viktor Mukhachev's comment, use SSLSocket over a Socket over a Proxy .结合 MacDaddy 的回答和 Viktor Mukhachev 的评论,使用SSLSocket over a Socket over a Proxy

Code:代码:

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class SSLThroughProxy {
    public static void main(String[] args) throws IOException {
        final String REQUEST = "GET / HTTP/1.1\r\n" +
                "Host: github.com\r\n" +
                "Connection: close\r\n" +
                "\r\n";

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("your-proxy-host", 8080));
        Socket socket = new Socket(proxy);

        InetSocketAddress address = new InetSocketAddress("github.com", 443);
        socket.connect(address);

        SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, address.getHostName(), address.getPort(), true);
        sslSocket.startHandshake();

        sslSocket.getOutputStream().write(REQUEST.getBytes());

        InputStream inputStream = sslSocket.getInputStream();
        byte[] bytes = inputStream.readAllBytes();
        System.out.println(new String(bytes, StandardCharsets.UTF_8));

        sslSocket.close();
    }
}

I don't have time to test/write a targetted solution now, however Upgrading socket to SSLSocket with STARTTLS: recv failed seems to cover the basic problem.我现在没有时间测试/编写有针对性的解决方案,但是使用 STARTTLS 将套接字升级到 SSLSocket: recv failed似乎涵盖了基本问题。

In your case, you need to connect to the proxy, issue a proxy connect, then upgrade the connection - essentially you CONNECT takes the place of the STARTTLS in the referenced question, and the check for " 670 " is not needed.在您的情况下,您需要连接到代理,发出代理连接,然后升级连接 - 本质上,您 CONNECT 代替了引用问题中的 STARTTLS,并且不需要检查“670”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM