使用ssl模块进行HTTPS代理隧道连接

Eli*_*ght 9 python ssl https proxy tunneling

我想手动(使用套接字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)
Run Code Online (Sandbox Code Playgroud)

上面的代码打印HTTP/1.1 200 Connection established加上一些标题,这是我所期望的.所以现在我应该准备好提出请求,例如

sock.sendall("GET / HTTP/1.1\r\n\r\n")
Run Code Online (Sandbox Code Playgroud)

但上面的代码返回

<!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>
Run Code Online (Sandbox Code Playgroud)

这也是有道理的,因为我仍然需要与example.com我正在隧道连接的服务器进行SSL握手.但是,如果不是立即发送GET我说的请求

sock = ssl.wrap_socket(sock)
Run Code Online (Sandbox Code Playgroud)

与远程服务器进行握手,然后我得到一个例外:

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
Run Code Online (Sandbox Code Playgroud)

那么如何与远程example.com服务器进行SSL握手呢?

编辑:我很确定在第二次调用之前没有其他数据可用,wrap_socket因为sock.recv(1)无限期地调用了块.

kra*_*etz 7

如果CONNECT字符串被重写如下,这应该有效:

CONNECT = "CONNECT %s:%s HTTP/1.0\r\nConnection: close\r\n\r\n" % (server, port)
Run Code Online (Sandbox Code Playgroud)

不知道为什么会这样,但也许它与我正在使用的代理有关.这是一个示例代码:

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()
Run Code Online (Sandbox Code Playgroud)

请注意首先打开套接字,然后打开放置在SSL上下文中的套接字.然后我手动初始化SSL握手.并输出:

已建立HTTP/1.1 200连接

<X509Name对象'/ C = US/ST =加州/ L =山景/ O = Google Inc/CN = mail.google.com'>

它基于pyOpenSSL,因为我也需要获取无效证书,Python内置的ssl模块将始终尝试验证证书是否已收到.


02s*_*ich 5

从OpenSSL和GnuTLS库的API来看,将SSLSocket堆叠到SSLSocket实际上并不是直接可行的,因为它们提供了特殊的读/写功能来实现加密,在包装预先存在的SSLSocket时它们无法自行使用. .

因此,错误是由内部SSLSocket直接从系统套接字读取而不是从外部SSLSocket读取引起的.这结束于发送不属于外部SSL会话的数据,这会严重终止并且肯定永远不会返回有效的ServerHello.

从中可以得出结论,我认为没有简单的方法来实现你(实际上我自己)想要实现的目标.


Sim*_*onJ 1

听起来你所做的事情并没有什么问题;当然可以调用wrap_socket()现有的SSLSocket.

如果在您调用时套接字上有额外的数据等待读取wrap_socket(),例如额外的\r\n或 HTTP 错误(由于服务器端缺少证书,例如实例)。您确定您已经阅读了当时所有可用的内容吗?

如果您可以强制第一个 SSL 通道使用“普通”RSA 密码(即非 Diffie-Hellman),那么您可以使用 Wireshark 解密流以查看发生了什么。