10.5.10 使用SSL
asyncio提供了对SSL的内置支持,可以在套接字上启用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.
# This name will mot 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)
客户端中需要做类似的修改。老版本中使用open_connection()来创建与服务器连接的套接字。
reader,writer = await asyncio.open_connection(*address)
同样需要一个SSLContext来保护套接字客户端的安全。这里对客户身份没有强制要求,所以只需要加载证书。
# The certificate is created with pymotw.com as the hostname.
# This name 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)
客户端中还需要做另一个很小的修改。由于SSL连接不支持发送文件末尾(EOF)通知,所以客户端使用一个NULL字节作为消息终止符。起那么老版本的客户端使用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()
# 新版本则发送一个0字节(b'\x00')来指示消息结束。
# This could be writer.writerlines() except that
# would make it harder EOF,so send a null byte to indicate
# the end of the meaasge.
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
在一个窗口中运行服务器,在另一个窗口中运行客户端。