Рубрики
Без рубрики

Использование SSL

Автор оригинала: Doug Hellmann.

asyncio имеет встроенную поддержку для включения связи SSL через сокеты. Передача экземпляра SSLContext сопрограммам, которые создают серверные или клиентские соединения, включает поддержку и гарантирует, что настройка протокола SSL позаботится о настройке протокола до того, как сокет будет представлен как готовый для использования приложением.

Эхо-сервер и клиент на основе сопрограмм из предыдущего раздела можно обновить с помощью нескольких небольших изменений. Первым шагом является создание файлов сертификата и ключей. Самозаверяющий сертификат можно создать с помощью такой команды:

$ openssl req -newkey rsa:2048 -nodes -keyout pymotw.key \
-x509 -days 365 -out pymotw.crt

Команда openssl запросит несколько значений, которые используются для создания сертификата, а затем создаст запрошенные выходные файлы.

Небезопасная установка сокета в предыдущем примере сервера использует start_server () для создания слушающего сокета.

factory  asyncio.start_server(echo, *SERVER_ADDRESS)
server  event_loop.run_until_complete(factory)

Чтобы добавить шифрование, создайте SSLContext с только что сгенерированным сертификатом и ключом, а затем передайте контекст в start_server () .

# The certificate is created with pymotw.com as the hostname,
# which will not match when the example code runs elsewhere,
# so disable hostname verification.
ssl_context  ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.check_hostname  False
ssl_context.load_cert_chain('pymotw.crt', 'pymotw.key')

# Create the server and let the loop finish the coroutine before
# starting the real event loop.
factory  asyncio.start_server(echo, *SERVER_ADDRESS,
                               sslssl_context)

Аналогичные изменения нужны и в клиенте. Старая версия использует open_connection () для создания сокета, подключенного к серверу.

reader, writer  await asyncio.open_connection(*address)

SSLContext снова необходим для защиты клиентской стороны сокета. Идентификация клиента не применяется, поэтому необходимо загрузить только сертификат.

# The certificate is created with pymotw.com as the hostname,
    # which will not match when the example code runs
    # elsewhere, so disable hostname verification.
    ssl_context  ssl.create_default_context(
        ssl.Purpose.SERVER_AUTH,
    )
    ssl_context.check_hostname  False
    ssl_context.load_verify_locations('pymotw.crt')
    reader, writer  await asyncio.open_connection(
        *server_address, sslssl_context)

Еще одно небольшое изменение необходимо в клиенте. Поскольку SSL-соединение не поддерживает отправку конца файла (EOF), клиент вместо этого использует нулевой байт в качестве признака завершения сообщения.

Старая версия клиентского цикла отправки использует write_eof () .

# This could be writer.writelines() except that
    # would make it harder to show each part of the message
    # being sent.
    for msg in messages:
        writer.write(msg)
        log.debug('sending {!r}'.format(msg))
    if writer.can_write_eof():
        writer.write_eof()
    await writer.drain()

Новая версия отправляет нулевой байт ( b '\ x00' ).

# This could be writer.writelines() except that
    # would make it harder to show each part of the message
    # being sent.
    for msg in messages:
        writer.write(msg)
        log.debug('sending {!r}'.format(msg))
    # SSL does not support EOF, so send a null byte to indicate
    # the end of the message.
    writer.write(b'\x00')
    await writer.drain()

Сопрограмма echo () на сервере должна искать NULL-байт и закрывать клиентское соединение при его получении.

async def echo(reader, writer):
    address  writer.get_extra_info('peername')
    log  logging.getLogger('echo_{}_{}'.format(*address))
    log.debug('connection accepted')
    while True:
        data  await reader.read(128)
        terminate  data.endswith(b'\x00')
        data  data.rstrip(b'\x00')
        if data:
            log.debug('received {!r}'.format(data))
            writer.write(data)
            await writer.drain()
            log.debug('sent {!r}'.format(data))
        if not data or terminate:
            log.debug('message terminated, closing connection')
            writer.close()
            return

Такой вывод дает запуск сервера в одном окне и клиента в другом.

$ python3 asyncio_echo_server_ssl.py
asyncio: Using selector: KqueueSelector
main: starting up on localhost port 10000
echo_::1_53957: connection accepted
echo_::1_53957: received b'This is the message. '
echo_::1_53957: sent b'This is the message. '
echo_::1_53957: received b'It will be sent in parts.'
echo_::1_53957: sent b'It will be sent in parts.'
echo_::1_53957: message terminated, closing connection
$ python3 asyncio_echo_client_ssl.py
asyncio: Using selector: KqueueSelector
echo_client: connecting to localhost port 10000
echo_client: sending b'This is the message. '
echo_client: sending b'It will be sent '
echo_client: sending b'in parts.'
echo_client: waiting for response
echo_client: received b'This is the message. '
echo_client: received b'It will be sent in parts.'
echo_client: closing
main: closing event loop