无法让 SSL 在 Tornado 上工作
Unable to get SSL working on Tornado
我正在尝试通过 SSL 实施 hiroakis 的项目 (https://github.com/hiroakis/tornado-websocket-example)。
我进行了必要的更改(见下文)并将证书颁发机构的 Public 证书添加到 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连接)为:
Index.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&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&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&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 public 证书 (创建证书时,必须有一个唯一的名称(可分辨名称 (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
今天终于找到问题的根源了。
当我创建证书时,在创建证书的 FQDN(完全限定域名)部分,我输入了 127.0.0.1 而不是 localhost)
令人惊讶的是,chrome 和 firefox 都没有提醒我我正在访问一个 CA 的网站,其 subject name 与目标的 主机名。
只有当我尝试使用 curl、curl https://localhost:8888/
时,它才会提醒我。
我认为浏览器应该这样做,不是吗?
我还注意到我的 /etc/hosts
文件已 127.0.0.1
映射到 localhost
。那为什么用curl传数据到localhost失败,到127.0.0.1却成功?
我正在尝试通过 SSL 实施 hiroakis 的项目 (https://github.com/hiroakis/tornado-websocket-example)。
我进行了必要的更改(见下文)并将证书颁发机构的 Public 证书添加到 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连接)为:
Index.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&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&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&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 public 证书 (创建证书时,必须有一个唯一的名称(可分辨名称 (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
今天终于找到问题的根源了。
当我创建证书时,在创建证书的 FQDN(完全限定域名)部分,我输入了 127.0.0.1 而不是 localhost)
令人惊讶的是,chrome 和 firefox 都没有提醒我我正在访问一个 CA 的网站,其 subject name 与目标的 主机名。
只有当我尝试使用 curl、curl https://localhost:8888/
时,它才会提醒我。
我认为浏览器应该这样做,不是吗?
我还注意到我的 /etc/hosts
文件已 127.0.0.1
映射到 localhost
。那为什么用curl传数据到localhost失败,到127.0.0.1却成功?