Utilizar SSL¶
asyncio
tiene soporte incorporado para habilitar la comunicación SSL en
conectores, Pasar una instancia SSLContext
a las co-rutinas que crean
conexiones de servidor o cliente habilita el soporte y asegura que la
configuración del protocolo SSL se realiza antes de que el conector se presenta
como listo para el uso de la aplicación.
El servidor de eco basado en co-rutina y el cliente de la sección anterior se pueden actualizar con unos pequeños cambios. El primer paso es crear el certificado y los archivos de llaves. Un certificado auto-firmado puede ser creado con un comando como:
$ openssl req -newkey rsa:2048 -nodes -keyout pymotw.key \
-x509 -days 365 -out pymotw.crt
El comando openssl
pedirá varios valores que se utilizan para generar el
certificado, y luego produce los archivos de salida requeridos.
La configuración insegura de conector en el servidor ejemplo anterior usa
start_server()
para crear el conector de escucha.
factory = asyncio.start_server(echo, *SERVER_ADDRESS)
server = event_loop.run_until_complete(factory)
Para agregar encriptación, crea un SSLContext
con el certificado y la llave
acabas de generar y luego pasa el contexto a 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)
Se necesitan cambios similares en el cliente. La versión antigua usa
open_connection()
para crear el conector conectado al servidor.
reader, writer = await asyncio.open_connection(*address)
Se necesita un SSLContext
nuevamente para asegurar el lado del cliente del
conector. La identidad del cliente no se está haciendo cumplir, por lo que solo
se necesita cargar el certificado.
# 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, ssl=ssl_context)
Se necesitan otros pequeños cambios en el cliente. Porque la conexión SSL no admite el envío de un final de archivo (EOF), el cliente utiliza un byte NULO como un terminador de mensaje en su lugar.
La versión anterior del bucle de envío del cliente usa 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()
La nueva versión envía un byte cero (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()
La co-rutina echo()
en el servidor debe buscar el byte NULL y cerrar la
conexión del cliente cuando se reciba.
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
Ejecutar el servidor en una ventana, y el cliente en otra, produce esta salida.
$ 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