13

我想手动(使用socketssl模块)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)无限期地阻塞。

4

5 回答 5

9

如果 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 连接建立

<X509Name object '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com'>

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

于 2012-03-16T13:46:46.083 回答
5

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

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

综上所述,我想说没有简单的方法来实现你(实际上是我自己)想要完成的事情。

于 2013-11-26T20:31:38.353 回答
2

最后,我在@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")

不要介意 custom cihpers=,那只是因为我不想处理证书。

还有 depth-1 ssl 输出,显示CONNECT我对它的响应ssagd以及 depth-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�.�*�&amp;���=5�����
��/�+�'�#��     ����g@32��ED���l4�F�1�-�)�%���&lt;/�A������
                                                        ��      ������
                                                                      �;��A��q�J&O��y�l
于 2013-12-02T15:01:14.983 回答
1

听起来你正在做的事情没有任何问题。当然可以调用wrap_socket()现有的SSLSocket.

如果在您调用的点有额外的数据等待在套接字上读取,则可能会发生“未知协议”错误(除其他原因外)wrap_socket(),例如额外\r\n或 HTTP 错误(由于服务器端缺少证书,例如实例)。你确定你当时已经阅读了所有可用的东西吗?

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

于 2010-12-08T23:15:27.583 回答
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。

于 2019-04-10T03:27:03.457 回答