无法让 SSL 在 Tornado 上工作

Posted

技术标签:

【中文标题】无法让 SSL 在 Tornado 上工作【英文标题】:Unable to get SSL working on Tornado 【发布时间】:2015-03-24 21:16:02 【问题描述】:

我正在尝试通过 SSL 实现 hiroakis 的项目 (https://github.com/hiroakis/tornado-websocket-example)。

我进行了必要的更改(见下文)并将证书颁发机构的公共证书添加到 Firefox 的受信任证书列表中。 当我打开https://localhost:8888 时,我得到了

SSLError: [SSL: SSLV3_ALERT_BAD_CERTIFICATE] SSLv3 alert bad certificate (_ssl.c:1750)

(整个追溯):

WARNING:tornado.general:error on read
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 630, in _handle_read
    pos = self._read_to_buffer_loop()
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 600, in _read_to_buffer_loop
    if self._read_to_buffer() == 0:
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 712, in _read_to_buffer
    chunk = self.read_from_fd()
  File "/usr/local/lib/python2.7/dist-packages/tornado/iostream.py", line 1327, in read_from_fd
    chunk = self.socket.read(self.read_chunk_size)
  File "/usr/lib/python2.7/ssl.py", line 603, in read
    v = self._sslobj.read(len or 1024)
SSLError: [SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:1750)
ERROR:tornado.general:Uncaught exception
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/tornado/http1connection.py", line 691, in _server_request_loop
    ret = yield conn.read_response(request_delegate)
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 807, in run
    value = future.result()
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 209, in result
    raise_exc_info(self._exc_info)
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 810, in run
    yielded = self.gen.throw(*sys.exc_info())
  File "/usr/local/lib/python2.7/dist-packages/tornado/http1connection.py", line 166, in _read_message
    quiet_exceptions=iostream.StreamClosedError)
  File "/usr/local/lib/python2.7/dist-packages/tornado/gen.py", line 807, in run
    value = future.result()
  File "/usr/local/lib/python2.7/dist-packages/tornado/concurrent.py", line 209, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 3, in raise_exc_info
SSLError: [SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:1750)

这里是python代码:

from tornado import websocket, web, ioloop, httpserver
import json

cl = []

class IndexHandler(web.RequestHandler):
    def get(self):
        self.render("/var/www/html/index.html")

class SocketHandler(websocket.WebSocketHandler):
    def check_origin(self, origin):
        print "Connection Received from ",origin
        return True

    def open(self):
        if self not in cl:
            cl.append(self)

    def on_close(self):
        if self in cl:
            cl.remove(self)

class ApiHandler(web.RequestHandler):

    @web.asynchronous
    def get(self, *args):
        self.finish()
        id = self.get_argument("id")
        value = self.get_argument("value")
        data = "id": id, "value" : value
        data = json.dumps(data)
        for c in cl:
            c.write_message(data)

    @web.asynchronous
    def post(self):
        pass

app = web.Application([
    (r'/', IndexHandler),
    (r'/ws', SocketHandler),
    (r'/api', ApiHandler),
    (r'/(favicon.ico)', web.StaticFileHandler, 'path': '../'),
    (r'/(rest_api_example.png)', web.StaticFileHandler, 'path': './'),
])

if __name__ == '__main__':
    server = httpserver.HTTPServer(app, ssl_options = 
        "certfile": "/local_repo/keys/server.crt",
        "keyfile": "/local_repo/server.key",
    )
    server.listen(8888)                                                                                                                                                                                
    ioloop.IOLoop.instance().start()

除此之外,我修改了(r'/ws', SocketHandler) to (r'/wss', SocketHandler)

同理,修改后的index.html(使用javascript创建socket连接)为:

索引.html

<!DOCTYPE html>
<html>
<head>
  <title>tornado WebSocket example</title>
  <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.no-icons.min.css" rel="stylesheet">
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
</head>
<body>
  <div class="container">
    <h1>tornado WebSocket example</h1>
    <hr>
      WebSocket status : <span id="message"></span>
    <hr>
    <h3>The following table shows values by using WebSocket</h3>

      <div class="row">
        <div class="span4">
          <table class="table table-striped table-bordered table-condensed">
            <tr>
              <th>No.</th><th>id</th><th>value</th>
            </tr>
            <tr id="row1">
              <td> 1 </td><td> id 1 </td><td id="1"> 0 </td>
            </tr>
            <tr id="row2">
              <td> 2 </td><td> id 2 </td><td id="2"> 0 </td>
            </tr>
            <tr id="row3">
              <td> 3 </td><td> id 3 </td><td id="3"> 0 </td>
            </tr>
          </table>
        </div>
        <div class="span4">
          <table class="table table-striped table-bordered table-condensed">
            <tr>
              <th>No.</th><th>id</th><th>value</th>
            </tr>
            <tr id="row4">
              <td> 4 </td><td> id 4 </td><td id="4"> 0 </td>
            </tr>
            <tr id="row5">
              <td> 5 </td><td> id 5 </td><td id="5"> 0 </td>
            </tr>
            <tr id="row6">
              <td> 6 </td><td> id 6 </td><td id="6"> 0 </td>
            </tr>
          </table>
        </div>
        <div class="span4">
          <table class="table table-striped table-bordered table-condensed">
            <tr>
              <th>No.</th><th>id</th><th>value</th>
            </tr>
            <tr id="row7">
              <td> 7 </td><td> id 7 </td><td id="7"> 0 </td>
            </tr>
            <tr id="row8">
              <td> 8 </td><td> id 8 </td><td id="8"> 0 </td>
            </tr>
            <tr id="row9">
              <td> 9 </td><td> id 9 </td><td id="9"> 0 </td>
            </tr>
          </table>
        </div>
      </div>

    <hr>
    <h3>REST API examples (use appropriate certificates with curl)</h3>
    <ol>
      <li>Set the "id 1" value to 100
        <ul><li>curl "https://localhost:8888/api?id=1&amp;value=100"</li></ul>
      </li>
      <li>Set the "id 1" value to 300 ( The row No 1 will change to yellow )
        <ul><li>curl "https://localhost:8888/api?id=1&amp;value=300"</li></ul>
      </li>
      <li>Set The "id 1" value to 600 ( The row No 1 will change to red )
        <ul><li>curl "https://hiroakis.com:8888/api?id=1&amp;value=600"</li></ul>
      </li>
    </ol>
    <ul>
      <li>value 201 - 500 : change to yellow</li>
      <li>value 501 - : change to red</li>
    </ul>
    <img src="./rest_api_example.png"/>
  </div>
  <script>
    var ws = new WebSocket('wss://localhost:8888/ws');
    var $message = $('#message');

    ws.onopen = function()
      $message.attr("class", 'label label-success');
      $message.text('open');
    ;
    ws.onmessage = function(ev)
      $message.attr("class", 'label label-info');
      $message.hide();
      $message.fadeIn("slow");
      $message.text('recieved message');

      var json = JSON.parse(ev.data);
      $('#' + json.id).hide();
      $('#' + json.id).fadeIn("slow");
      $('#' + json.id).text(json.value);

      var $rowid = $('#row' + json.id);
      if(json.value > 500)
        $rowid.attr("class", "error");
      
      else if(json.value > 200)
        $rowid.attr("class", "warning");
      
      else
        $rowid.attr("class", "");
      
    ;
    ws.onclose = function(ev)
      $message.attr("class", 'label label-important');
      $message.text('closed');
    ;
    ws.onerror = function(ev)
      $message.attr("class", 'label label-warning');
      $message.text('error occurred');
    ;

  </script>
</body>
</html>

我使用以下步骤创建了 SSL 证书:

创建 CA 私钥:

openssl genrsa -des3 -out servercakey.pem 

创建 CA 公用证书 (当您创建证书时,必须有一个唯一名称(可分辨名称 (DN)),您创建的每个证书都不同) :

openssl req -new -x509 -key servercakey.pem -out root.crt 

创建服务器的私钥文件:

openssl genrsa -out server.key 

创建服务器证书请求:

openssl req -new -out reqout.txt -key server.key 

使用 CA 私钥文件签署服务器证书:

openssl x509 -req -in reqout.txt -days 3650 -sha1 -CAcreateserial -CA root.crt -CAkey servercakey.pem -out server.crt 

创建客户端的私钥文件:

openssl genrsa -out client.key 

创建客户端证书请求:

openssl req -new -out reqout.txt -key client.key 

使用 CA 私钥文件签署客户端证书:

openssl x509 -req -in reqout.txt -days 3650 -sha1 -CAcreateserial -CA root.crt -CAkey servercakey.pem -out client.crt 

为服务器创建 pem 文件:

cat server.crt root.crt > server.pem

【问题讨论】:

你用的是什么版本的python 我使用的是python 2.7.8 现在我收到另一个错误:SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:1750) 您是否真的在 /keys/ 文件夹中拥有证书而在此文件夹之外拥有密钥?这很不寻常。 tlsv1 alert unknown CA 被客户端发回,因为它不知道也不信任签署证书的 CA。需要将 CA 添加到客户端的信任库中。如何做到这一点取决于客户。 【参考方案1】:

今天终于找到问题的根源了。

当我创建证书时,在创建证书的 FQDN(完全限定域名)部分,我输入了 127.0.0.1 而不是 localhost)

令人惊讶的是,chrome 和 firefox 都没有提醒我,我正在使用 CA 访问的网站,其 主题名称 与目标的 主机名 不匹配。

只有当我尝试使用 curlcurl https://localhost:8888/ 时,它才提醒我。

我认为浏览器应该这样做,不是吗?

我还注意到我的/etc/hosts 文件将127.0.0.1 映射到localhost。那为什么使用curl传数据到localhost失败,传到127.0.0.1成功呢?

【讨论】:

以上是关于无法让 SSL 在 Tornado 上工作的主要内容,如果未能解决你的问题,请参考以下文章

Centos7部署tornado项目

无法从Tornado Client连接到基于Tornado SSL的服务器

相互SSL身份验证 - 客户端和服务器端。 Python - &gt; Django / Twisted / Tornado

Tornado异步阻塞解决方案

Tornado WebSocketHandler 不会响应 SSL 请求

无法让 SSL 在 Node.js 应用程序中工作