Как установить TLS-соединение, используя python? - PullRequest
3 голосов
/ 03 августа 2020

Я хочу создать соединение TLS с сервером. Затем я хочу отправить некоторые зашифрованные данные на сервер. Я знаю имя хоста и порт, и у меня есть сертификат. Удивительно, но я также получил закрытый ключ сервера. Однако я думаю, что это ненормально, что я получил закрытый ключ.

Первый вопрос заключается в том, действительно ли мне нужен закрытый ключ для создания TLS-соединения?

Кстати, я использую этот python скрипт

import socket
import ssl

server_addr = '**.**.**.**'
server_port = ****
server_cert = 'server.crt'
server_key  = 'server.key'        # I use the private key

context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile=server_cert, keyfile=server_key)

bindsocket = socket.socket()
bindsocket.connect((server_addr, server_port))

Я использую закрытый ключ в приведенном выше скрипте. Работает без ошибок. Однако, когда я пытаюсь использовать bind () вместо connect (), т.е.

bindsocket.bind((server_addr, server_port))

, я получаю следующую ошибку:

OSError: [Errno 99] Невозможно назначить запрошенный адрес

Я прочитал много связанных вопросов о вышеуказанной ошибке, однако я до сих пор не понимаю, почему это происходит. Поскольку у меня есть имя хоста, порт, сертификат и ключ, я рассчитываю успешно создать TLS-соединение.

Второй вопрос заключается в том, как я могу sh установить TLS подключение? Правильно ли мой сценарий?

Я очень благодарен за любые комментарии по улучшению сценария.

1 Ответ

2 голосов
/ 06 августа 2020

Итак, во-первых, у вас ни в коем случае не должно быть закрытого ключа! Как следует из названия, это частное sh соединение и не требуется. У вас может быть ключ publi c, но даже в этом нет необходимости, если вы используете стандартный SSL и доверяете центру сертификации, подписавшему сертификат сервера. Вы уверены, что это закрытый ключ? Файл начинается с -----BEGIN PRIVATE KEY-----? Проверить с openssl rsa -noout -text -in server.key. См. статью в Википедии и этот пост для получения дополнительной информации об асимметрии c криптографии.

Далее по пути: С помощью socket.bind() вы привязываете сокет к порту на вашем локальном компьютере. Это невозможно, так как у вашего компьютера нет адреса (вы указываете адрес сервера). Судя по вашему коду, вы пытаетесь открыть сокет как сервер. Для этого вам понадобится закрытый ключ, но тогда вы будете принимать соединения, а не сами подключаться к другим машинам. У меня такое ощущение, что вы тут смешиваете две вещи. Обратитесь к python документации из socket.bind() и к этому вопросу , поскольку он кажется тесно связанным. Также ознакомьтесь с документацией python по ssl . Я взял пример, который выполняет то, что вы просите из указанной документации:

import socket, ssl, pprint

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# require a certificate from the server
ssl_sock = ssl.wrap_socket(s,
                           ca_certs="/etc/ca_certs_file",
                           cert_reqs=ssl.CERT_REQUIRED)
ssl_sock.connect(('www.verisign.com', 443))

pprint.pprint(ssl_sock.getpeercert())
# note that closing the SSLSocket will also close the underlying socket
ssl_sock.close()

Также посмотрите пример о том, как открыть сокет SSL в режиме сервера .

Дальнейшие мысли: Вам действительно нужно делать все это самому? Если сервер, например, использует HTTPS (SSL-шифрованный HTTP), вы можете просто использовать библиотеку http.client .

Не стесняйтесь спрашивать, если вам нужно, чтобы я что-то уточнить. Я соответствующим образом обновлю свой ответ.

EDIT: Как вы указали, вы хотите открыть порт в режиме сервера, я сделал для вас пример (он сильно опирается на пример документации python):

import socket, ssl

context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")

bindsocket = socket.socket()
bindsocket.bind(('127.0.0.1', 10023))
bindsocket.listen(5)

def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        print(data)
        data = connstream.recv(1024)

while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()

Запуск:

% python3 ssltest.py
b'hello server!\n'
b'this is data\n'

Клиентская сторона:

% openssl s_client -connect 127.0.0.1:10023
CONNECTED(00000005)
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify error:num=18:self signed certificate
verify return:1
depth=0 C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
verify return:1
---
Certificate chain
 0 s:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
   i:C = SE, ST = Some-State, O = Internet Widgits Pty Ltd
---
Server certificate
-----BEGIN CERTIFICATE-----
 ... certificate ...
-----END CERTIFICATE-----
subject=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

issuer=C = SE, ST = Some-State, O = Internet Widgits Pty Ltd

---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2272 bytes and written 404 bytes
Verification error: self signed certificate
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-GCM-SHA384
    ... session stuff ...
    Extended master secret: yes
---
hello server!
this is data
^C

Если вы используете какой-то обычный протокол, например HTTP, вам следует использовать библиотеку. Вот пример с flask:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> 
>>> @app.route("/")
... def hello():
...     return "Hello World!"
... 
>>> if __name__ == "__main__":
...     app.run(ssl_context=('cert.pem', 'key.pem'))
... 
 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [06/Aug/2020 11:45:50] "GET / HTTP/1.1" 200 -
...