【问题标题】:How to connect a SSL socket through a HTTP proxy?如何通过 HTTP 代理连接 SSL 套接字?
【发布时间】:2015-03-14 19:25:43
【问题描述】:

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

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

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

这适用于浏览器,因为代理建立 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 永远不会返回。通过代理机器的wireshark转储,我可以看到代理和服务器之间发生了tcp握手。通过网络会话的转储,我可以看到客户端通常会在此时启动 SSL 握手,这在我的场景中不会发生。

这不是信任管理器的问题,因为证书永远不会返回给我,也永远不会被验证。

编辑:

经过讨论,这是我尝试运行的代码的更完整版本。上面这个以简单 (new 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 函数的套接字,而不是像我原来的示例中那样即时创建一个新套接字。

【问题讨论】:

  • java.net.Proxy 在 HTTP 下不起作用。它似乎需要 SOCKS 代理。我找到了一个适用于 sun os 的补丁,但没有找到适用于 android 的补丁。这对于 HTTP 至关重要,因为 Android 无法在没有 3rd 方应用的情况下提供 HTTPS/SOCKS 支持(默认安装不提供足够的配置 @ 2015 年 1 月)。
  • 如果您通过 HTTP 代理连接到 SSL 套接字,我建议尽可能考虑使用 websockets - 特别是因为错误消息“java.net.ConnectException: failed to connect to /192.168 .1.100(端口 443)”显示尝试连接到标准 HTTPS 端口。当然,这取决于您的用例....

标签: java android ssl


【解决方案1】:

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

要为您自己的SSLSocket 工作,您只需创建一个纯文本套接字,在其上发出HTTP CONNECT 命令,检查响应是否为200,然后将其包装在@987654327 中@

EDIT 发送CONNECT命令时,当然不能关闭socket;并且在阅读其回复时,您不得使用BufferedReader,,否则您将丢失数据;要么手动阅读该行,要么使用DataInputStream.readLine(),,尽管它已被弃用。您还需要完全遵循 RFC 2616。

【讨论】:

  • 当您使用接受代理的 Socket 构造函数时,Java 8 支持 HTTP 隧道代理:new Socket(new Proxy(Proxy.Type.HTTP, socketAddress))
【解决方案2】:

你必须使用 javax.net lib 。您可以使用 javax.net.ssl.* 归档到您的目标。

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

SSLSocketClientWithTunneling

【讨论】:

  • 那个链接有一点点错误。其中一条请求行以\n, 终止,应为\r\n.
【解决方案3】:

结合 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();
    }
}

【讨论】:

    【解决方案4】:

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

    在您的情况下,您需要连接到代理,发出代理连接,然后升级连接 - 本质上您 CONNECT 代替了引用问题中的 STARTTLS,并且不需要检查“670”。

    【讨论】:

    • 但是需要检查 HTTP 200
    猜你喜欢
    • 2012-02-29
    • 2015-09-23
    • 2014-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 2021-08-10
    相关资源
    最近更新 更多