Using SSLΒΆ

asyncio has built-in support for enabling SSL communication on sockets. Passing an SSLContext instance to the coroutines that create server or client connections enables the support and ensures that the SSL protocol setup is taken care of before the socket is presented as ready for the application to use.

The coroutine-based echo server and client from the previous section can be updated with a few small changes. The first step is to create the certificate and key files. A self-signed certificate can be created with a command like:

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

The openssl command will prompt for several values that are used to generate the certificate, and then produce the output files requested.

The insecure socket setup in the previous server example uses start_server() to create the listening socket.

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

To add encryption, create an SSLContext with the certificate and key just generated and then pass the context to 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,
                               ssl=ssl_context)
server = event_loop.run_until_complete(factory)

Similar changes are needed in the client. The old version uses open_connection() to create the socket connected to the server.


    # This could be writer.writelines() except that

An SSLContext is needed again to secure the client-side of the socket. Client identity is not being enforced, so only the certificate needs to be loaded.

    # 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')

        *server_address, ssl=ssl_context)

One other small changes is needed in the client. Because the SSL connection does not support sending an end-of-file (EOF), the client closes its outgoing connection to the server to signal that it is done.

The old version of the client send loop uses write_eof().

    # 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()

The new version uses close().

    # 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))
    await writer.drain()

    # SSL does not support sending EOF, so close the write socket
    # instead.
    writer.close()

Running the server in one window, and the client in another, produces this output.

$ python3 asyncio_echo_server_ssl.py
asyncio: Using selector: KqueueSelector
main: starting up on localhost port 10000
echo_::1_55235: connection accepted
echo_::1_55235: received b'This is the message. '
echo_::1_55235: sent b'This is the message. '
echo_::1_55235: received b'It will be sent in parts.'
echo_::1_55235: sent b'It will be sent in parts.'
echo_::1_55235: closing
$ 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
asyncio: returning true from eof_received() has no effect when using ssl
echo_client: closing
main: closing event loop