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

时间:2015-01-16 08:20:23

标签: java android ssl

我正在尝试使用Java(Android)连接到具有SSL套接字的服务器。请注意,这不是HTTP数据。这是专有协议,混合了文本和二进制数据。

我想通过HTTP代理中继SSL连接,但我遇到了很多问题。现在我使用的场景以及我的浏览器似乎与squid代理一起使用的场景如下

[client] - > [http connection] - > [proxy] - > [ssl connection] - > [server]

这适用于浏览器,因为在代理进行ssl连接后,会立即进行TLS协商。但是我的代码似乎没有这样做。

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);

我遇到的问题是createSocket NEVER RETURNS。通过代理机器的wireshark转储,我可以看到代理和服务器之间发生了tcp握手。通过Web会话转储,我可以看到客户端通常会在此时启动SSL握手,这在我的方案中不会发生。

这对信任管理员来说不是问题,因为证书永远不会回复给我,而且永远不会验证。

编辑:

经过讨论,这是我正在尝试运行的代码的更完整版本。上面的这个版本使用简单的(新的Socket(...))作为参数,这是我后来尝试过的。

我正在尝试调试的代码的原始版本抛出 java.net.ConnectException: failed to connect to /192.168.1.100 (port 443): connect failed: ETIMEDOUT (Connection timed out)

序列如下(再次简化):

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]

然后使用以下代码阅读响应:

    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 )
    {
    }

我可以说这段代码有效,因为它没有SSL。这里创建的套接字proxySocket是传递给createSocket函数的套接字,而不是像我原来的例子一样动态创建一个新的套接字。

4 个答案:

答案 0 :(得分:14)

java.net.Proxyhttps.proxyHost/proxyPort属性仅支持通过HttpURLConnection,进行HTTP代理,而非Socket.

要使自己的SSLSocket工作,您需要创建一个纯文本套接字,在其上发出HTTP CONNECT命令,检查200的响应,然后将其换行在SSLSocket.

编辑发送CONNECT命令时,当然不能关闭套接字;在阅读回复时,您不能使用BufferedReader,,否则您将丢失数据;尽管已弃用,但要么手动阅读,要么使用DataInputStream.readLine(),。您还需要完全遵循RFC 2616。

答案 1 :(得分:6)

您必须使用 javax.net lib 。您可以使用 javax.net.ssl。*

存档到目标

我认为您可以使用oracle docs获得解决方案。这是链接。

SSLSocketClientWithTunneling

答案 2 :(得分:1)

我现在没有时间测试/编写目标解决方案,但Upgrading socket to SSLSocket with STARTTLS: recv failed似乎涵盖了基本问题。

在您的情况下,您需要连接到代理,发出代理连接,然后升级连接 - 基本上您在引用的问题中取代了STARTTLS,并检查&#34; 670&#34;不需要。

答案 3 :(得分:0)

组合MacDaddy的答案和Viktor Mukhachev的评论,在SSLSocket上的Socket上使用Proxy

代码:

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();
    }
}
相关问题