我想手动(使用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 Connection established
加上一些标题,这是我所期望的。所以现在我应该准备好提出请求,例如
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
那么如何与远程example.com
服务器进行SSL握手?
编辑:我非常确定在第二次调用wrap_socket
之前没有其他数据可用,因为无限期地调用sock.recv(1)
阻止。
答案 0 :(得分:7)
如果按如下方式重写CONNECT字符串,这应该有效:
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
不确定为什么会这样,但也许它与我正在使用的代理有关。这是一个示例代码:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("proxy.example.com", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(CONNECT)
print s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print cert.get_subject()
ss.shutdown()
ss.close()
注意首先打开套接字然后打开放在SSL上下文中的套接字。然后我手动初始化SSL握手。并输出:
建立HTTP / 1.1 200连接
&lt; X509Name object'/ C = US / ST = California / L = Mountain View / O = Google Inc / CN = mail.google.com'&gt;
它基于pyOpenSSL,因为我也需要获取无效的证书,Python内置的ssl模块将始终尝试验证证书是否已收到。
答案 1 :(得分:5)
从OpenSSL和GnuTLS库的API判断,将SSLSocket堆叠到SSLSocket上实际上并不是直接可行的,因为它们提供了特殊的读/写功能来实现加密,它们在包装pre时无法自行使用 - 现有的SSLSocket。
因此,错误是由内部SSLSocket直接从系统套接字读取而不是从外部SSLSocket读取引起的。这结束于发送不属于外部SSL会话的数据,这会严重结束并且肯定永远不会返回有效的ServerHello。
总而言之,我认为没有简单的方法来实现你(实际上我自己)想要实现的目标。
答案 2 :(得分:1)
这听起来并不像你在做什么有什么问题;当然可以在现有的wrap_socket()
上致电SSLSocket
。
如果在您调用wrap_socket()
的点上等待在套接字上读取额外数据,可能会发生“未知协议”错误(除其他原因外),例如额外的\r\n
或HTTP错误(例如,由于服务器端缺少证书)。你确定你已经阅读了那时的所有内容吗?
如果您可以强制第一个SSL通道使用“普通”RSA密码(即非Diffie-Hellman),那么您可以使用Wireshark解密流以查看正在发生的情况。
答案 3 :(得分:1)
最后,我在@kravietz和@ 02strich的答案上进行了扩展。
这是代码
import threading
import select
import socket
import ssl
server = 'mail.google.com'
port = 443
PROXY = ("localhost", 4433)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
class ForwardedSocket(threading.Thread):
def __init__(self, s, **kwargs):
threading.Thread.__init__(self)
self.dest = s
self.oursraw, self.theirsraw = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
self.theirs = socket.socket(_sock=self.theirsraw)
self.start()
self.ours = ssl.wrap_socket(socket.socket(_sock=self.oursraw), **kwargs)
def run(self):
rl, wl, xl = select.select([self.dest, self.theirs], [], [], 1)
print rl, wl, xl
# FIXME write may block
if self.theirs in rl:
self.dest.send(self.theirs.recv(4096))
if self.dest in rl:
self.theirs.send(self.dest.recv(4096))
def recv(self, *args):
return self.ours.recv(*args)
def send(self, *args):
return self.outs.recv(*args)
def test():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY)
s = ssl.wrap_socket(s, ciphers="ALL:aNULL:eNULL")
s.send(CONNECT)
resp = s.read(4096)
print (resp, )
fs = ForwardedSocket(s, ciphers="ALL:aNULL:eNULL")
fs.send("foobar")
不要介意自定义cihpers=
,这只是因为我不想处理证书。
还有深度为1的ssl输出,显示CONNECT
,我对它的响应ssagd
和深度-2 ssl协商和二进制垃圾:
[dima@bmg ~]$ openssl s_server -nocert -cipher "ALL:aNULL:eNULL"
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
-----BEGIN SSL SESSION PARAMETERS-----
MHUCAQECAgMDBALAGQQgmn6XfJt8ru+edj6BXljltJf43Sz6AmacYM/dSmrhgl4E
MOztEauhPoixCwS84DL29MD/OxuxuvG5tnkN59ikoqtfrnCKsk8Y9JtUU9zuaDFV
ZaEGAgRSnJ81ogQCAgEspAYEBAEAAAA=
-----END SSL SESSION PARAMETERS-----
Shared ciphers: [snipped]
CIPHER is AECDH-AES256-SHA
Secure Renegotiation IS supported
CONNECT mail.google.com:443 HTTP/1.0
Connection: close
sagq
�u\�0�,�(�$��
�"�!��kj98���� �m:��2�.�*�&���=5�����
��/�+�'�#�� ����g@32��ED���l4�F�1�-�)�%���</�A������
�� ������
�;��A��q�J&O��y�l
答案 4 :(得分:0)
以@kravietz答案为基础。这是一个通过Squid代理在Python3中工作的版本:
from OpenSSL import SSL
import socket
def verify_cb(conn, cert, errun, depth, ok):
return True
server = 'mail.google.com'
port = 443
PROXY_ADDR = ("<proxy_server>", 3128)
CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(PROXY_ADDR)
s.send(str.encode(CONNECT))
s.recv(4096)
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_verify(SSL.VERIFY_PEER, verify_cb)
ss = SSL.Connection(ctx, s)
ss.set_connect_state()
ss.do_handshake()
cert = ss.get_peer_certificate()
print(cert.get_subject())
ss.shutdown()
ss.close()
这也适用于Python 2。