Я работаю над проектом с использованием flask-socketio и python-socketio. Для обеспечения безопасности связи необходимо обновить систему с помощью SSL. Я работаю с Mac и Python 3.7.
Я создал сертификат с помощью openssl (rootCA.pem
) и добавил его в связку ключей Mac. После этого я выдал server.cert
и server.key
с rootCA.pem
. Я построил веб-сервер с помощью flask-socketio, добавил server.cert
и server.key
на его стороне. В конце концов, веб-сайт хорошо работал с ssl.
Проблема возникла, когда я захотел защитить связь между сервером flask-socketio и клиентом python-socketio. Когда клиент пытался соединиться с сервером, соединение было отклонено и установлено unknown ca error
:
ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2488)
Я провел некоторые исследования и понял, что эта проблема может быть связана с тем, что Python 3.7 использует свою собственную частную копиюOpenSSL. Из-за этого клиент SocketIO не смог проверить мой самозаверяющий сертификат. И это информация из установки Python 3.7:
Этот вариант Python 3.7 включает в себя собственную частную копию OpenSSL 1.1.1. Устаревшие поставляемые Apple библиотеки OpenSSL больше не используются. Это означает, что сертификаты доверия в системных и пользовательских цепочках ключей, управляемых приложением Keychain Access и утилитой командной строки безопасности, больше не используются в качестве значений по умолчанию модулем Python ssl. Пример сценария команды включен в / Applications/ Python 3.7 для установки курируемого пакета корневых сертификатов по умолчанию из стороннего пакета certifi (https://pypi.org/project/certifi/). Если вы решите использовать certifi, вам следует рассмотреть возможность подписки на службу обновления электронной почты проекта, чтобы получать уведомления при получении пакета сертификатовобновлен.
Итак, я пошел в папку /Applications/Python 3.7
и выполнил Install Certificates.command
.
Однако, это не ключ к проблеме. тщательно отобранная коллекция корневых сертификатов . Конечно, она не содержит мой самозаверяющий сертификат. Поэтому, чтобы решить эту проблему, я должен позволить Python найти rootCA.pem
.
В папке сертификата python находится документ с именем cacert.pem
, в котором содержится Root. Сертификаты. Поэтому я добавил содержимое rootCA.pem
в конце cacert.pem
.
После этого я попробовал этот код в командной строке:
openssl verify -CAfile cacert.pem server.crt
И вывод:
server.crt: OK
Думаю, в этом случае cacert.pem
может проверить сертификат сервера. Я знаю, что это не элегантное решение. Пожалуйста, дайте мне знать, если у вас есть лучший способ заставить python найти мой самозаверяющий сертификат.
После этого я попытался подключиться к серверу flask-socketio с помощью python-socketio. На этот раз я получил еще одну ошибку. На стороне сервера информация журнала:
(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
А на стороне клиента ошибка выдается вот так:
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
Я понятия не имею, почему это происходит. Связь между сервером flask-socketio и клиентом python-socketio работает без SSL. Так что я думаю, что с кодом может быть все в порядке, но я все же приведу код здесь. И это для сервера:
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)
Это для клиента:
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")
Пожалуйста, помогите! Я знаю, что мой вопрос многословен. Но я просто хочу показать, как я пытался решить проблему. Если что-то не так, пожалуйста, дайте мне знать. Спасибо!