java 如何通过 HTTP 代理连接 SSL 套接字?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27979752/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to connect a SSL socket through a HTTP proxy?
提问by Eric
I'm trying to use Java (Android) to connect to a server with a SSL socket. Please note that this is not HTTP data. This is proprietary protocol with a mix of text and binary data.
我正在尝试使用 Java (Android) 连接到带有 SSL 套接字的服务器。请注意,这不是 HTTP 数据。这是一种混合了文本和二进制数据的专有协议。
I want to relay that SSL connection through a HTTP proxy, but I am facing a lot of problems with that. Right now the scenario that I use and that my browser seems to use with a squid proxy is as follow
我想通过 HTTP 代理中继该 SSL 连接,但我面临很多问题。现在我使用的场景以及我的浏览器似乎与鱿鱼代理一起使用的场景如下
[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. However my code does not seem to do that.
这适用于浏览器,因为在代理建立 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);
The problem that I have is that createSocket NEVER RETURNS. With a wireshark dump from the proxy machine, I can see that a tcp handshake takes place between the proxy and the server. 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.
我遇到的问题是 createSocket 永远不会返回。使用来自代理机器的 wireshark 转储,我可以看到在代理和服务器之间发生了 tcp 握手。通过 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 throwsjava.net.ConnectException: failed to connect to /192.168.1.100 (port 443): connect failed: ETIMEDOUT (Connection timed out)
我试图调试的代码的原始版本抛出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. 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.
我可以说这段代码有效,因为它在没有 SSL 的情况下有效。此处创建的套接字proxySocket
是传递给 createSocket 函数的套接字,而不是像在我的原始示例中那样即时创建一个新套接字。
回答by user207421
java.net.Proxy
, or the https.proxyHost/proxyPort
properties, only support HTTP proxying via HttpURLConnection,
not via a Socket.
java.net.Proxy
或https.proxyHost/proxyPort
属性,仅支持 HTTP 代理,HttpURLConnection,
而不是通过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.
EDITWhen sending the CONNECT command, you must not close the socket, of course; and when reading its reply you must not use a BufferedReader,
otherwise you will lose data; either read the line by hand or use DataInputStream.readLine(),
despite its deprecation. You also need to follow RFC 2616 entirely.
EDIT发送 CONNECT 命令时,当然不能关闭套接字;并且在阅读其回复时,您不得使用 aBufferedReader,
否则您将丢失数据;要么手动阅读该行,要么使用DataInputStream.readLine(),
它,尽管它已被弃用。您还需要完全遵循 RFC 2616。
回答by MacDaddy
You have to use javax.net lib. you can archive to your target using javax.net.ssl.*.
您必须使用javax.net lib。您可以使用javax.net.ssl.*存档到您的目标。
I think you can get solution using oracle docs. Here is the link for that.
我认为您可以使用 oracle docs 获得解决方案。这是链接。
回答by Phil Lello
I don't have time to test/write a targetted solution now, however Upgrading socket to SSLSocket with STARTTLS: recv failedseems to cover the basic problem.
我现在没有时间测试/编写有针对性的解决方案,但是将套接字升级到 SSLSocket 与 STARTTLS: 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”。