在Python中通过TCP代理连接发送SSL数据
我遇到了以下情况:
我必须使用一个HTTP代理来连接到一个HTTPS服务器。由于一些原因,我需要访问原始数据(也就是加密之前的数据),所以我选择使用socket库,而不是一些专门处理HTTP的库。
因此,我首先连接一个TCP socket到HTTP代理,并发送连接命令。
在这个时候,HTTP代理接受了连接,并似乎将所有后续的数据转发到目标服务器。
但是,如果我现在尝试切换到SSL,我会收到以下错误信息:
error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
这表明socket尝试与HTTP代理进行握手,而不是与HTTPS目标服务器进行握手。
这是我目前写的代码:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('proxy',9502))
s.send("""CONNECT en.wikipedia.org:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:15.0) Gecko/20100101 Firefox/15.0.1
Proxy-Connection: keep-alive
Host: en.wikipedia.org
""")
print s.recv(1000)
ssl = socket.ssl(s, None, None)
ssl.connect(("en.wikipedia.org",443))
在连接到HTTP代理后,正确的方式是什么,以便打开一个SSL socket连接到目标服务器?
2 个回答
0
这个内容适用于Python 3。
< proxy > 是一个IP地址或者域名。
< port > 是代理服务器监听的端口,可能是443、80,或者其他你设置的端口。
< endpoint > 是你想通过代理连接的最终服务器。
< cn > 是一个可选的SNI字段,最终服务器可能会需要这个字段。
import socket,ssl
def getcert_sni_proxy(cn,endpoint,PROXY_ADDR=("<proxy>", <port>)):
#prepare the connect phrase
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (endpoint, 443)
#connect to the actual proxy
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.connect(PROXY_ADDR)
conn.send(str.encode(CONNECT))
conn.recv(4096)
#set the cipher for the ssl layer
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
#connect to the final endpoint via the proxy, sending an optional servername information [cn here]
sock = context.wrap_socket(conn, server_hostname=cn)
#retreive certificate from the server
certificate = ssl.DER_cert_to_PEM_cert(sock.getpeercert(True))
return certificate
1
(一般来说,使用现成的HTTPS库,比如PyCurl,会比自己实现要简单得多。)
首先,别把你的变量命名为 ssl
。这个名字已经被 ssl
模块用了,你不想把它给覆盖掉。
其次,别再第二次使用 connect
。你已经连接上了,现在需要做的是包装这个套接字。因为Python默认不进行证书验证,所以你需要验证远程证书和主机名。
下面是具体步骤:
- 先建立一个明文连接,像前面几行那样使用
CONNECT
。 - 读取你收到的HTTP响应,确保你得到的是200状态码。(你需要逐行读取头部信息。)
- 使用
ssl_s = ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLS1, ca_certs='/path/to/cabundle.pem')
来包装这个套接字。然后,验证主机名。值得一读的是 这个回答:关于connect
方法以及它在包装套接字后做了什么。 - 最后,像使用普通套接字一样使用
ssl_s
。不要再调用connect
了。