我该如何在PyOpenSSL
中阻止SSL协议,转而使用TLS
?我正在使用CentOS 7
,具体版本如下:
pyOpenSSL-0.13.1-3.el7.x86_64
openssl-1.0.1e-34.el7_0.7.x86_64
在我的配置文件中(这是一个CherryPy应用程序),我有以下内容:
'server.ssl_module': 'pyopenssl',
今天对于CherryPy来说,这是一个非常好的问题。本月我们开始讨论CherryPy在py2.6+ ssl
和pyOpenSSL上的包装器的整体可维护性和SSL问题(CherryPy用户组)。我计划在那里发布有关SSL问题的话题,所以您可以订阅该组以获取更多详细信息。
目前,以下是可能的。我使用Debian Wheezy、Python 2.7.3-4+deb7u1和OpenSSL 1.0.1e-2+deb7u16。我从repo中安装了CherryPy(3.6已经破坏了SSL),并安装了pyOpenSSL 0.14。我尝试覆盖CherryPy的两个SSL适配器以在Qualys SSL实验室(测试)中获得一些分数。这非常有帮助,我强烈建议您使用它来测试部署(无论您的前端是CherryPy还是其他内容)。
因此,基于ssl
的适配器仍然存在漏洞,我看不到在py2 < 2.7.9(大规模SSL更新)和py3 < 3.3中解决问题的方法。由于CherryPy ssl
适配器是在这些更改之前编写的,因此需要重写以支持新旧两种方式(主要是SSL Contexts)。另一方面,使用子类化的pyOpenSSL适配器基本上还可以,但有以下问题:
SSL.OP_SINGLE_DH_USE
可能有所帮助,但实际上并没有。也可能取决于OpenSSL的版本。这是代码。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import ssl
import cherrypy
from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter
from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
from cherrypy import wsgiserver
if sys.version_info < (3, 0):
from cherrypy.wsgiserver.wsgiserver2 import ssl_adapters
else:
from cherrypy.wsgiserver.wsgiserver3 import ssl_adapters
try:
from OpenSSL import SSL
except ImportError:
pass
ciphers = (
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
'!eNULL:!MD5:!DSS:!RC4:!SSLv2'
)
bundle = os.path.join(os.path.dirname(cherrypy.__file__), 'test', 'test.pem')
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8443,
'server.thread_pool' : 8,
'server.ssl_module' : 'custom-pyopenssl',
'server.ssl_certificate' : bundle,
'server.ssl_private_key' : bundle,
}
}
class BuiltinSsl(BuiltinSSLAdapter):
'''Vulnerable, on py2 < 2.7.9, py3 < 3.3:
* POODLE (SSLv3), adding ``!SSLv3`` to cipher list makes it very incompatible
* can't disable TLS compression (CRIME)
* supports Secure Client-Initiated Renegotiation (DOS)
* no Forward Secrecy
Also session caching doesn't work. Some tweaks are posslbe, but don't really
change much. For example, it's possible to use ssl.PROTOCOL_TLSv1 instead of
ssl.PROTOCOL_SSLv23 with little worse compatiblity.
'''
def wrap(self, sock):
"""Wrap and return the given socket, plus WSGI environ entries."""
try:
s = ssl.wrap_socket(
sock,
ciphers = ciphers, # the override is for this line
do_handshake_on_connect = True,
server_side = True,
certfile = self.certificate,
keyfile = self.private_key,
ssl_version = ssl.PROTOCOL_SSLv23
)
except ssl.SSLError:
e = sys.exc_info()[1]
if e.errno == ssl.SSL_ERROR_EOF:
# This is almost certainly due to the cherrypy engine
# 'pinging' the socket to assert it's connectable;
# the 'ping' isn't SSL.
return None, {}
elif e.errno == ssl.SSL_ERROR_SSL:
if e.args[1].endswith('http request'):
# The client is speaking HTTP to an HTTPS server.
raise wsgiserver.NoSSLError
elif e.args[1].endswith('unknown protocol'):
# The client is speaking some non-HTTP protocol.
# Drop the conn.
return None, {}
raise
return s, self.get_environ(s)
ssl_adapters['custom-ssl'] = BuiltinSsl
class Pyopenssl(pyOpenSSLAdapter):
'''Mostly fine, except:
* Secure Client-Initiated Renegotiation
* no Forward Secrecy, SSL.OP_SINGLE_DH_USE could have helped but it didn't
'''
def get_context(self):
"""Return an SSL.Context from self attributes."""
c = SSL.Context(SSL.SSLv23_METHOD)
# override:
c.set_options(SSL.OP_NO_COMPRESSION | SSL.OP_SINGLE_DH_USE | SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
c.set_cipher_list(ciphers)
c.use_privatekey_file(self.private_key)
if self.certificate_chain:
c.load_verify_locations(self.certificate_chain)
c.use_certificate_file(self.certificate)
return c
ssl_adapters['custom-pyopenssl'] = Pyopenssl
class App:
@cherrypy.expose
def index(self):
return '<em>Is this secure?</em>'
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
我知道有两种方法可以做到这一点。一种是配置选项,另一种是运行时选项。
配置选项
在构建 OpenSSL 时使用配置选项。它适用于所有应用程序,因为它应用了您的管理策略并解决了不考虑 SSL/TLS 相关问题的应用程序。
对于此选项,只需使用 no-ssl2 no-ssl3
配置 OpenSSL 即可。通常还会使用 no-comp
,因为压缩可能会泄露信息。
./Configure no-ssl2 no-ssl3 <other opts>
还有其他的 OpenSSL 选项可供选择,您可能想要访问 OpenSSL 维基上的 编译和安装。
运行时选项
在 C 中,您需要 (1) 使用 2/3 方法获取 SSL 2/3 及以上版本;然后 (2) 调用 SSL_CTX_set_options
(或 SSL_set_options
)并 (3) 移除 SSL 协议。这将只留下 TLS 协议:
SSL_CTX* ctx = SSL_CTX_new(SSLv23_method());
const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION;
SSL_CTX_set_options(ctx, flags);
OpenSSL.SSL.Context.set_options
来完成。_util
模块前面的下划线表示它是私有的。这不是为应用程序开发人员准备的,而是pyOpenSSL内部使用的。正确的API是OpenSSL.SSL.Context.set_options
。 - Jean-Paul Calderone