用Java实现一个简单的HTTPS代理应用程序?

7
我正在编写一个简单的Java HTTPS代理程序,用于教育目的。我的程序侦听端口(例如 7443)以接收来自浏览器(例如 Firefox )的HTTPS请求,解析请求并将其转发到所需的目标(例如https://www.comodo.com)。Firefox的代理设置已设置为在SSL连接时使用我的端口(127.0.0.1 : 7443)。我的代码简短明了:
static // initializer
{
    System.setProperty("javax.net.ssl.keyStore", "MyKeyStore");
    System.setProperty("javax.net.ssl.keyStorePassword", "password");
}

SSLServerSocketFactory ssFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

try {
    SSLServerSocket listener = (SSLServerSocket) ssFactory.createServerSocket(port, 64);
    listener.setUseClientMode(false);
    listener.setWantClientAuth(false);
    listener.setNeedClientAuth(false);

    SSLSocket connection = (SSLSocket) listener.accept();
    browser.startHandshake();  /*  <<==  Exception throws at this line  */

} catch (IOException ex) {
    ex.printStackTrace(System.err);
}

但是我遇到了以下异常:

    javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

异常消息表示连接可能是明文的,但仅限于从 Firefox 发出的 HTTPS 连接使用该端口。我已经记录了 Firefox 发送到我的应用程序的内容,如下所示:
CONNECT www.comodo.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:20.0) Gecko/20100101 Firefox/20.0
Proxy-Connection: keep-alive
Connection: keep-alive
Host: www.comodo.com

火狐浏览器显示明文,我想CONNECT是一个SOCKS命令(虽然我不确定),而我在火狐的SOCKS设置中没有设置任何内容。以下是火狐代理设置的屏幕截图:Firefox Proxy Settings。我错过了什么?我需要做什么才能使它与Firefox或任何其他浏览器一起工作?对于那些认为这是另一个问题的重复,并且已经在其他问题中得到解答的人,我必须说:是的,这两个问题都源于类似的问题,但是引用问题中唯一的答案指向使用SSL套接字,这被证明是误导性的,并导致了这个新问题。因此,尽管它们旨在解决类似的问题,但这个问题展示了一个完全不同但误导性的解决问题的路径,因此它可能为未来面临这种问题的人提供有用的指导。

1
这不是引用问题的副本。两个问题显然不同,引用的问题中没有可接受的解决方案。 - Seyed Mohammad
对于那些来到这个帖子的人:你们可能会对这里的_TheConstructor_的工作示例感兴趣:https://dev59.com/8mQo5IYBdhLWcg3wE70J - Trinimon
2个回答

7

摆脱所有的SSL。只需处理传入的CONNECT命令,与上游服务器建立明文连接,然后开始复制字节。浏览器和服务器将使用SSL进行通信,但您完全不需要。


我认为这不正确...我之前尝试过这个解决方案,但它没有起作用...你可以查看我的先前关于此解决方案的问题,网址为http://stackoverflow.com/questions/14153662/java-https-connection-forwarding - Seyed Mohammad
1
当然是正确的。请参阅RFC。显然,浏览器正在以明文发送CONNECT命令,并且您可以推断它期望以明文方式接收回复,如果您在两个方向上传输所有其他字节而不进行进一步干预,则任何剩余的问题必须在服务器或浏览器端解决。您需要比“它没有工作”更具体。您引用的另一个问题也没有提供任何进一步的信息。 - user207421
就像我之前所说的,我已经尝试过你提出的方法,实际上这是我首先想到的方法,但它失败了。正如我提供的其他问题中所述,将浏览器的CONNECT请求转发到服务器总是导致“连接重置”错误,这意味着服务器没有响应并关闭了连接...这种行为很明显,因为服务器正在侦听443端口上的SSL/TLSHELLO消息,而它得到了一个CONNECT,这不是预期的SSL/TLS消息。 - Seyed Mohammad
1
@SeyedMohammad,你的代理服务器不应该转发CONNECT请求,而是应该处理它,即与服务器建立一个纯TCP连接并返回200 OK。然后,你的代理服务器应该简单地中继客户端和服务器之间的流量。SSL/TLS层将在这个中继连接的顶部,但代理本身不会参与其中。 - Bruno
@Bruno 谢谢你的建议,我会尝试一下并告诉你是否成功。只是为了确保我理解正确,当我收到浏览器的“CONNECT”命令时,我应该建立与目标服务器的连接,连接成功后向浏览器返回“200 OK”,然后只需转发浏览器和服务器之间的字节... 是这样吗? - Seyed Mohammad
1
这正是我们一直在说的。没有人说要转发CONNECT命令。所以你根本没有“尝试过这个”。而且仅仅重复说你“尝试过这个”而不展示代码是没有用的。 - user207421

4
您的设置使用HTTP隧道,其中发送到代理的初始请求进行SSL加密;由于启用了SSL的套接字正在等待SSL握手,因此会抛出异常。
在这种机制中,客户端使用“CONNECT” HTTP方法请求HTTP代理服务器将TCP连接转发到所需目标。然后,服务器代表客户端继续建立连接。一旦服务器建立了连接,代理服务器就会继续代理TCP流到客户端和从客户端。请注意,仅初始连接请求是HTTP-之后,服务器只是代理已建立的TCP连接。
您可以在HTTP Tunneling维基页面上了解更多信息。要查看此操作,请启动netcat服务器并将Firefox代理设置为指向该端口:
nc -l 8000

现在在Firefox中输入https://www.google.com,并查看nc输出:
CONNECT www.google.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) 
Proxy-Connection: keep-alive
Connection: keep-alive
Host: www.google.com

而且这是完全的明文。下面的图表演示了 Firefox 代理期望如何进行通信。

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接