Reputation: 485
I am working on a project using flask-socketio and python-socketio. In order to secure the communication, the system needs to be upgraded with SSL. I am working with Mac and Python 3.7.
I created a certificate using openssl (rootCA.pem
) and added it to the Mac's keychain. After that, I issued server.cert
and server.key
with the rootCA.pem
. I built the web server using flask-socketio, added server.cert
and server.key
on its side. In the end, the website worked well with ssl.
The problem happened when I wanted to secure the communication between flask-socketio server and python-socketio client. When the client tried to connect with server, the connection was refused and raised unknown ca error
:
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2488)
I did some researches and realized that this problem may be due to that Python 3.7 uses its own private copy of OpenSSL. Because of that, the SocketIO client was unable to verify my self-signed certificate. And this is the information from Python 3.7's installation:
This variant of Python 3.7 includes its own private copy of OpenSSL 1.1.1. The deprecated Apple-supplied OpenSSL libraries are no longer used. This means that the trust certificates in system and user keychains managed by the Keychain Access application and the security command line utility are no longer used as defaults by the Python ssl module. A sample command script is included in /Applications/Python 3.7 to install a curated bundle of default root certificates from the third-party certifi package (https://pypi.org/project/certifi/). If you choose to use certifi, you should consider subscribing to the project's email update service to be notified when the certificate bundle is updated.
So I went to the folder of /Applications/Python 3.7
and executed the Install Certificates.command
.
However, it is not the key to the problem. Because python certifi is a carefully curated collection of Root Certificates. It, of course, does not contain my self-signed certificate. So, to solve the problem, I need to let the Python find the rootCA.pem
.
In the folder of python certifi, there is a document named cacert.pem
. This contains the Root Certificates. So I added the content of rootCA.pem
in the end of cacert.pem
.
After that, I tried this code in the command line:
openssl verify -CAfile cacert.pem server.crt
And the output:
server.crt: OK
I think, in this case, the cacert.pem
can verify the certificate of the server. I know it is not an elegant solution. Please let me know if you have better way to make python find my self-signed certificate.
After that, I tried to connect to flask-socketio server with the python-socketio. This time, I got another error. On the server side, the log information is:
(57183) accepted ('127.0.0.1', 56322)
0fd838477c534dfda802bfb0d130d358: Sending packet OPEN data {'sid': '0fd838477c534dfda802bfb0d130d358', 'upgrades': ['websocket'], 'pingTimeout': 60000, 'pingInterval': 25000}
0fd838477c534dfda802bfb0d130d358: Sending packet MESSAGE data 0
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=polling&EIO=3&t=1570706436.665149 HTTP/1.1" 200 349 0.000436
(57183) accepted ('127.0.0.1', 56324)
http://localhost:3000 is not an accepted origin.
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=websocket&EIO=3&sid=0fd838477c534dfda802bfb0d130d358&t=1570706436.685631 HTTP/1.1" 400 122 0.000182
And on the client side, the error was thrown like this:
Traceback (most recent call last):
File "simple_client.py", line 18, in <module>
sio.connect('https://localhost:3000', namespaces="/channel_A")
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/socketio/client.py", line 262, in connect
engineio_path=socketio_path)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 170, in connect
url, headers, engineio_path)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 308, in _connect_polling
if self._connect_websocket(url, headers, engineio_path):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 346, in _connect_websocket
cookie=cookies)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 514, in create_connection
websock.connect(url, **options)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 226, in connect
self.handshake_response = handshake(self.sock, *addrs, **options)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 79, in handshake
status, resp = _get_resp_headers(sock)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 160, in _get_resp_headers
raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
websocket._exceptions.WebSocketBadStatusException: Handshake status 400 BAD REQUEST
I have no idea why does it happen. The communication between flask-socketio server and python-socketio client works well without SSL. So I think there might be nothing wrong with the code, but I still give the code here. And this is for server:
from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_socketio import Namespace
import eventlet
app = Flask(__name__, template_folder="templates")
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, engineio_logger=True, logger=True)
# Create a URL route in our application for "/"
@app.route('/')
def home():
"""
This function loads the homepage
"""
return render_template('index.html')
class MyCustomNamespace(Namespace):
def on_connect(self):
print("Client just connected")
def on_disconnect(self):
print("Client just left")
def on_messages(self, data):
print(f"\nReceived data from client: \n {data}\n")
return data
socketio.on_namespace(MyCustomNamespace('/channel_A'))
if __name__ == "__main__":
eventlet.wsgi.server(
eventlet.wrap_ssl(eventlet.listen(("localhost", 3000)),
certfile='server.crt',
keyfile='server.key',
server_side=True), app)
# socketio.run(app, host="localhost", port=3000, debug=True)
This is for client:
import socketio
sio = socketio.Client()
def message_received(data):
print(f"Message {data} received")
@sio.on('connect', namespace="/channel_A")
def on_connect():
print("Connect...")
@sio.on('disconnect', namespace="/channel_A")
def on_disconnect():
print(f"Disconnected from server")
sio.connect('https://localhost:3000', namespaces="/channel_A")
Please help! I know my question is wordy. But I just want to show the way I tried to solve the problem. If there is anything wrong, please let me know. Thank you!
Upvotes: 2
Views: 6889
Reputation: 67479
Recent versions of Flask-SocketIO come configured with the most secure settings with regards to cross-origins setups, which is to only allow the same origin. If your frontend app and your Flask app are running on different servers or ports, then you have to configure cross-origin so that they can work together.
For example, if your frontend is hosted at http://localhost:3000
, you can allow that as an origin with:
socketio = SocketIO(app, cors_allowed_origins='http://localhost:3000')
Upvotes: 3