I am trying to implement hiroakis's project (https://github.com/hiroakis/tornado-websocket-example) over SSL.
I made required changes (see below) and also added the Certificate Authority's Public Certificate to Firefox's trusted certificate list.
When I open https://localhost:8888, I get
SSLError: [SSL: SSLV3_ALERT_BAD_CERTIFICATE] SSLv3 alert bad certificate (_ssl.c:1750)
(Entire Traceback):
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)
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()
Apart from that, I modified (r'/ws', SocketHandler) to (r'/wss', SocketHandler)
Similary, the modified index.html (which uses javascript to create socket connection) is:
<!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>
I created SSL certificates using these steps:
Create the CA private key:
openssl genrsa -des3 -out servercakey.pem
Create the CA public certificate (When you create a certificate, there must be one unique name (a Distinguished Name (DN)), which is different for each certificate that you create):
openssl req -new -x509 -key servercakey.pem -out root.crt
Create the server's private key file:
openssl genrsa -out server.key
Create the server certificate request:
openssl req -new -out reqout.txt -key server.key
Use the CA private key file to sign the server's certificate:
openssl x509 -req -in reqout.txt -days 3650 -sha1 -CAcreateserial -CA root.crt -CAkey servercakey.pem -out server.crt
Create the client's private key file:
openssl genrsa -out client.key
Create the client certificate request:
openssl req -new -out reqout.txt -key client.key
Use the CA private key file to sign the client's certificate:
openssl x509 -req -in reqout.txt -days 3650 -sha1 -CAcreateserial -CA root.crt -CAkey servercakey.pem -out client.crt
Creating pem file for Server:
cat server.crt root.crt > server.pem
Finally today I was able to find the source of problem.
When I was creating the certificates, in the FQDN (fully qualified domain name) part of creating certificate, I had entered 127.0.0.1 instead of localhost)
Whats surprising is that neither chrome nor firefox alerted me that I was accessing a website with the CA whose subject name did not match the target's host name.
It was only when I tried with curl, curl https://localhost:8888/ that it alerted me.
I think browsers are supposed to do that, don't they?
I also noted that my /etc/hosts file has 127.0.0.1 mapped to localhost. Then why is it that transferring data using curl to localhost fails but to 127.0.0.1 succeeds?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With