使用 ssl 模块的 HTTPS 代理隧道
我想手动(使用 socket 和 ssl 模块)通过本身使用 HTTPS
的代理发出 HTTPS
请求>。
我可以很好地执行初始 CONNECT
交换:
import ssl, socket
PROXY_ADDR = ("proxy-addr", 443)
CONNECT = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"
sock = socket.create_connection(PROXY_ADDR)
sock = ssl.wrap_socket(sock)
sock.sendall(CONNECT)
s = ""
while s[-4:] != "\r\n\r\n":
s += sock.recv(1)
print repr(s)
上面的代码打印 HTTP/1.1 200 Connectionbuilt
以及一些标头,这正是我所期望的。所以现在我应该准备好发出请求,例如
sock.sendall("GET / HTTP/1.1\r\n\r\n")
,但上面的代码返回
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</body></html>
这也是有道理的,因为我仍然需要与我通过隧道连接到的 example.com
服务器进行 SSL 握手。但是,如果我不是立即发送 GET 请求,而是
sock = ssl.wrap_socket(sock)
与远程服务器进行握手,那么我会得到一个异常:
Traceback (most recent call last):
File "so_test.py", line 18, in <module>
ssl.wrap_socket(sock)
File "/usr/lib/python2.6/ssl.py", line 350, in wrap_socket
suppress_ragged_eofs=suppress_ragged_eofs)
File "/usr/lib/python2.6/ssl.py", line 118, in __init__
self.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [Errno 1] _ssl.c:480: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
那么如何与远程示例进行 SSL 握手。 com 服务器?
编辑:我很确定在第二次调用 wrap_socket
之前没有可用的其他数据,因为调用 sock.recv(1)
会无限期地阻塞。
I'd like to manually (using the socket and ssl modules) make an HTTPS
request through a proxy which itself uses HTTPS
.
I can perform the initial CONNECT
exchange just fine:
import ssl, socket
PROXY_ADDR = ("proxy-addr", 443)
CONNECT = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"
sock = socket.create_connection(PROXY_ADDR)
sock = ssl.wrap_socket(sock)
sock.sendall(CONNECT)
s = ""
while s[-4:] != "\r\n\r\n":
s += sock.recv(1)
print repr(s)
The above code prints HTTP/1.1 200 Connection established
plus some headers, which is what I expect. So now I should be ready to make the request, e.g.
sock.sendall("GET / HTTP/1.1\r\n\r\n")
but the above code returns
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.<br />
</body></html>
This makes sense too, since I still need to do an SSL handshake with the example.com
server to which I'm tunneling. However, if instead of immediately sending the GET
request I say
sock = ssl.wrap_socket(sock)
to do the handshake with the remote server, then I get an exception:
Traceback (most recent call last):
File "so_test.py", line 18, in <module>
ssl.wrap_socket(sock)
File "/usr/lib/python2.6/ssl.py", line 350, in wrap_socket
suppress_ragged_eofs=suppress_ragged_eofs)
File "/usr/lib/python2.6/ssl.py", line 118, in __init__
self.do_handshake()
File "/usr/lib/python2.6/ssl.py", line 293, in do_handshake
self._sslobj.do_handshake()
ssl.SSLError: [Errno 1] _ssl.c:480: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
So how can I do the SSL handshake with the remote example.com
server?
EDIT: I'm pretty sure that no additional data is available before my second call to wrap_socket
because calling sock.recv(1)
blocks indefinitely.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
从 OpenSSL 和 GnuTLS 库的 API 来看,将 SSLSocket 堆叠到 SSLSocket 上实际上并不是直接可能的,因为它们提供了特殊的读/写函数来实现加密,而在包装预先存在的 SSLSocket 时它们无法使用它们自己。
因此,该错误是由内部 SSLSocket 直接从系统套接字读取而不是从外部 SSLSocket 读取引起的。这最终会发送不属于外部 SSL 会话的数据,这会以糟糕的方式结束,并且肯定永远不会返回有效的 ServerHello。
由此得出的结论是,我想说没有简单的方法可以实现您(实际上是我自己)想要完成的任务。
Judging from the API of the OpenSSL and GnuTLS library, stacking a SSLSocket onto a SSLSocket is actually not straightforwardly possible as they provide special read/write functions to implement the encryption, which they are not able to use themselves when wrapping a pre-existing SSLSocket.
The error is therefore caused by the inner SSLSocket directly reading from the system socket and not from the outer SSLSocket. This ends in sending data not belonging to the outer SSL session, which ends badly and for sure never returns a valid ServerHello.
Concluding from that, I would say there is no simple way to implement what you (and actually myself) would like to accomplish.
最后我得到了扩展@kravietz 和@02strich 答案的地方。
这是代码
不要介意自定义
cihpers=
,那只是因为我不想处理证书。还有深度 1 ssl 输出,显示 CONNECT,我对它的响应 ssagd 和深度 2 ssl 协商和二进制垃圾:
Finally I got somewhere expanding on @kravietz and @02strich answers.
Here's the code
Don't mind custom
cihpers=
, that only because I didn't want to deal with certificates.And there's depth-1 ssl output, showing
CONNECT
, my response to itssagd
and depth-2 ssl negotiation and binary rubbish:听起来你所做的事情并没有什么问题;当然可以在现有的
SSLSocket
上调用wrap_socket()
。如果在调用
wrap_socket()
时套接字上有额外的数据等待读取,例如额外的\r\ ,则可能会出现“未知协议”错误(除其他原因外) n
或 HTTP 错误(例如,由于服务器端缺少证书)。您确定您已经阅读了当时所有可用的内容吗?如果您可以强制第一个 SSL 通道使用“普通”RSA 密码(即非 Diffie-Hellman),那么您可以使用 Wireshark 解密流以查看发生了什么。
It doesn't sound like there's anything wrong with what you're doing; it's certainly possible to call
wrap_socket()
on an existingSSLSocket
.The 'unknown protocol' error can occur (amongst other reasons) if there's extra data waiting to be read on the socket at the point you call
wrap_socket()
, for instance an extra\r\n
or an HTTP error (due to a missing cert on the server end, for instance). Are you certain you've read everything available at that point?If you can force the first SSL channel to use a "plain" RSA cipher (i.e. non-Diffie-Hellman) then you may be able to use Wireshark to decrypt the stream to see what's going on.
基于@kravietz 的回答。这是一个通过 Squid 代理在 Python3 中工作的版本:
这也可以在 Python 2 中工作。
Building on @kravietz answer. Here is a version that works in Python3 through a Squid proxy:
This works in Python 2 also.
如果 CONNECT 字符串重写如下,这应该可以工作:
不确定为什么会这样,但也许它与我正在使用的代理有关。下面是一个示例代码:
请注意如何首先打开套接字,然后打开放置在 SSL 上下文中的套接字。然后我手动初始化 SSL 握手。并输出:
它基于 pyOpenSSL,因为我也需要获取无效证书,并且 Python 内置 ssl 模块将始终尝试验证证书(如果收到)。
This should work if the CONNECT string is rewritten as follows:
Not sure why this works, but maybe it has something to do with the proxy I'm using. Here's an example code:
Note how the socket is first opened and then open socket placed in SSL context. Then I manually initialize SSL handshake. And output:
It's based on pyOpenSSL because I needed to fetch invalid certificates too and Python built-in ssl module will always try to verify the certificate if it's received.