wrap_socket()函数收到了一个意外的关键字参数'server_hostname'?

3

我想使用Python测试Web服务器。我对Python几乎没有经验,但因为它易学和简单易用(这是别人的看法,不是我的),所以鼓励我使用它。我使用的脚本如下:

s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = ssl.wrap_socket(s1,
                     ca_certs="./pki/signing-dss-cert.pem",
                     cert_reqs=ssl.CERT_REQUIRED,
                     ssl_version=ssl.PROTOCOL_TLSv1,
                     server_hostname="localhost")

s2.connect( ("localhost", 8443) )

s2.send("GET / ")
time.sleep(1)
s2.send("HTTP/1.1")

错误信息为:
Traceback (most recent call last):
  File "./fetch.sh", line 10, in <module>
    server_hostname="localhost")
TypeError: wrap_socket() got an unexpected keyword argument 'server_hostname'

我还尝试使用servernamenamehostnamesni但都没有成功。
Python文档中没有提到SNI(用于套接字对象的TLS / SSL包装器SSL wiki页面)。但是我知道SNI和server_hostname的补丁在4年前的2010年被纳入了(SSLContext.wrap_socket添加*server_hostname*参数changeset 65593:846c0e1342d0)。
我需要访问的等效OpenSSL调用是SSL_set_tlsext_host_name
如何指定SNI主机名? (最终,我需要将其设置为任意名称,因为我正在测试代理)。

尝试将我的代码与你的代码合并,并在创建连接时尝试使用服务器名称而不是“localhost”。 - pasztorpisti
你使用的是哪个版本的Python? - Bruno
sys.version_info(major=2, minor=7, micro=3, releaselevel='final', serial=0)。Debian 7.3(x64),已完全打补丁。 - jww
3个回答

2

先连接,然后再包装套接字。

import socket, ssl
sock = socket.create_connection( ('localhost', 443) )
sock = ssl.wrap_socket(sock, ca_certs="./pki/signing-dss-cert.pem", cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1)

在Python2中,有时我也会使用以下技巧(因为httplib.HTTPSConnection文档表示它不会对https服务器证书进行任何检查):

import urllib, httplib, ssl

PATH = urllib.quote(u'/'.encode('utf-8'))
conn = httplib.HTTPConnection('www.google.com', 443)
conn.connect()
try:
    conn.sock = ssl.wrap_socket(conn.sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=CA_BUNDLE_FILE, ssl_version=VERSION)
    conn.request('GET', PATH)
    resp = conn.getresponse()
    print resp.status
    print resp.read()
finally:
    conn.close()

请注意,如果您想与服务器通信,则使用HTTP客户端通常比玩弄原始套接字要容易得多。

1
@noloader,恐怕这个简单的SSL实现没有暴露这样的选项。 - pasztorpisti
@noloader,我在你的帖子中没有看到我的代码,所以它不起作用并不让我感到惊讶。 - pasztorpisti
感谢pasztorpisti。请原谅我的无知...上面的代码似乎没有设置服务器名称,这正是我想要实现的。在您的示例中,假设您想连接到google.com,但为了进行测试,将TLS服务器名称扩展设置为example.com - jww
@noloader 在我看来,如果您使用正确的域名建立连接而不是使用“localhost”,那么应该可以工作。 - pasztorpisti
@nloader 我是一名C/C++程序员,但我总是在Python中设置测试http(s)客户端和服务器。在我看来,用C/C++编写这些东西是浪费时间的,而在Python中,只要你写好了,你的东西就可以立即跨平台。顺便说一句,你试过我推荐的吗?你可以轻松地复制粘贴并修改第二个示例,它实际上连接到某个地方,发送请求并接收响应。我的答案中的第二个代码块执行了完整的https请求。你使用Python 2还是3?因为它们不同... - pasztorpisti
显示剩余13条评论

2

你提到的补丁是针对Python 3.2的,而你正在使用Python 2.7。问题5639似乎也表明没有计划将SNI支持向后移植到Python 2.7。

相反,你可以使用pyOpenSSL包装套接字,它的Connection类自版本0.13以来具有set_tlsext_host_name方法。(我不确定Debian 7.3附带哪个版本,如果需要,您可能需要设置虚拟环境并升级到新版本。)

在pyOpenSSL存储库中有一个SNI示例

如果要使您对wrap_socket的使用更兼容替换httplib连接中的sock值的技巧,则可以查看urllib3如何使用pyOpenSSL。它基本上是从现有套接字创建了一个OpenSSL.SSL.Connection,但由于该连接与套接字不兼容,因此将其包装到实现所需方法的类中。

(顺便说一下,在Python 2.7中,urlliburllib2httpconnection根本不进行任何证书验证,除非您自己通过包装它们的套接字来实现它。)

编辑:

这里是一个应该适用于Python 3.2的版本的代码。不幸的是,server_name参数不在普通的ssl.wrap_socket中,只在SSLContext.wrap_socket中,但您可以直接使用SSLSocket

import socket
import ssl

CA_BUNDLE_FILE="/etc/ssl/certs/ca-certificates.crt"


HOST = "sni.velox.ch"
s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s2 = ssl.SSLSocket(sock=s1, ca_certs=CA_BUNDLE_FILE,
                     cert_reqs=ssl.CERT_REQUIRED,
                     ssl_version=ssl.PROTOCOL_TLSv1,
                     server_hostname=HOST)

s2.connect((HOST, 443))

s2.send(bytes("GET / HTTP/1.1\n", "UTF-8"))
# This might need to be modified when using another port
s2.send(bytes("Host: %s\n" % (HOST,), "UTF-8"))
s2.send(bytes("\n", "UTF-8"))

# Certainly not the best way to read the response, but it works.
while True:
    x = s2.read()
    if not x:
        break
    print(x)

谢谢Bruno。"如果您想让您对wrap_socket的使用更兼容..." - 我只是想让它工作。因为它应该更容易和更快,所以我今天浪费了大约4小时。我想我要回到C/C++。对我来说,C/C++的开发速度比试图解开你上面说的一切要快得多。而且我很困惑最新的Debian正在使用一个旧的、未修补的库。我使用最新的发行版有理由,而这个原因不是因为我喜欢前沿的错误。 - jww
1
我不会因为这个问题就否定Python。如果你是Python的新手,也没有任何2.x版本的遗留代码,那么你可以尝试使用Python 3.2+解释器。只要你已经安装了相应的软件包,它应该可以通过python3.2进行调用。 - Bruno
再次感谢Bruno。一旦我下载、构建和安装了Python 3.4,这个问题就解决了。 - jww
很奇怪你不得不自己获取Python 3.4并构建它。在Debian系统上,我只需安装“python3”软件包(并使用“python3.2”)即可使其工作。(以防您不知道,在Debian / Ubuntu上,Python版本可以通过普通软件包一起安装(除了微级数字)。默认情况下,“python”仍然是最新的2.x解释器,但您可以显式地调用“python3.x”解释器。) - Bruno

0

我的环境是:requests==2.7.0,python-2.7.5-34.el7.x86_64,gevent==1.0.2

将Python版本更改为python-2.7.5-18.el7_1.1.x86_64,问题得到解决。

在CentOS上:

sudo rpm -Uvh --oldpackage python-devel-2.7.5-18.el7_1.1.x86_64.rpm  python-libs-2.7.5-18.el7_1.1.x86_64.rpm  python-2.7.5-18.el7_1.1.x86_64.rpm

可以在谷歌上搜索包。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接