如何阻止 SSL 协议以支持 TLS?

Posted

技术标签:

【中文标题】如何阻止 SSL 协议以支持 TLS?【英文标题】:How to block SSL protocols in favor of TLS? 【发布时间】:2015-05-29 10:27:18 【问题描述】:

如何阻止 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',

【问题讨论】:

【参考方案1】:

我知道有两种方法可以做到这一点。一个是配置选项,另一个是运行时选项。

配置选项

配置选项在构建 OpenSSL 时使用。它非常适合所有应用程序,因为它应用您的管理策略并解决不注意 SSL/TLS 相关问题的应用程序。

对于此选项,只需使用 no-ssl2 no-ssl3 配置 OpenSSL。 no-comp 也经常使用,因为压缩会泄漏信息。

./Configure no-ssl2 no-ssl3 <other opts>

还有其他可用的 OpenSSL 选项,您可能想访问 OpenSSL 的 wiki 上的 Compilation and Installation。

运行时选项

在 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);

在 Python 中,您可以使用 OpenSSL.SSL.Context.set_options

【讨论】:

_util 模块上的前导 _ 表示它是私有的。它不适用于应用程序开发人员,它是 pyOpenSSL 内部的。正确的 API 是 OpenSSL.SSL.Context.set_options 感谢 Jean-Paul Calderone;固定。【参考方案2】:

这对于今天的 CherryPy 来说真是个好问题。本月,我们开始在 CherryPy user group 中讨论 CherryPy 包装器在 py2.6+ ssl 和 pyOpenSSL 上的 SSL 问题和整体可维护性。我正在那里计划一个关于 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 实验室test 中获得一些分数。它非常有用,我强烈建议您使用它来测试您的部署(无论您的前端是什么,CherryPy 与否)。

因此,基于ssl 的适配器仍然存在漏洞,我看不到在 py2 ssl 适配器是在这些更改之前很久就编写的,所以它需要重写以支持新旧方式(主要是 SSL Contexts)。另一方面,使用子类 pyOpenSSL 适应它大部分都很好,除了:

启用Secure Client-Initiated Renegotiation。它可能依赖于 OpenSSL。 不,Forward Secrecy,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)

更新

这里是 the article 和 discussion 应该决定 CherryPy 的 SSL 支持的未来。

【讨论】:

UPD:现在 wsgiserver2 和 wsgiserver3 没有区别 谢谢你——我使用这个示例代码来配置 python ftpserver 模块 pyftpdlib 如何使用 openssl 来限制它支持的不需要的 SSL 密码的数量。对于任何感兴趣的人,我使用了很棒的 testssl.sh 脚本来测试它(testssl.sh)

以上是关于如何阻止 SSL 协议以支持 TLS?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 TLS/SSL 确保 WebSocket 连接的安全

SSL和TLS协议如何提供身份验证机密性和完整性

在 Tomcat 中配置 SSL/TLS 以支持 HTTPS

如何使用 TLS/SSL 确保套接字连接的安全

http服务(nginxapache)停用不安全的SSL协议TLS1.0和TLS1.1协议/启用TLS1.3

TLS协议的TLS握手协议