11.10 在网络服务中加入SSL

    模块能为底层socket连接添加SSL的支持。ssl.wrap_socket() 函数接受一个已存在的socket作为参数并使用SSL层来包装它。例如,下面是一个简单的应答服务器,能在服务器端为所有客户端连接做认证。

    下面我们演示一个客户端连接服务器的交互例子。客户端会请求服务器来认证并确认连接:

    1. >>> from socket import socket, AF_INET, SOCK_STREAM
    2. >>> import ssl
    3. >>> s = socket(AF_INET, SOCK_STREAM)
    4. >>> s_ssl = ssl.wrap_socket(s,
    5. cert_reqs=ssl.CERT_REQUIRED,
    6. ca_certs = 'server_cert.pem')
    7. >>> s_ssl.connect(('localhost', 20000))
    8. >>> s_ssl.send(b'Hello World?')
    9. 12
    10. >>> s_ssl.recv(8192)
    11. b'Hello World?'
    12. >>>

    这种直接处理底层socket方式有个问题就是它不能很好的跟标准库中已存在的网络服务兼容。例如,绝大部分服务器代码(HTTP、XML-RPC等)实际上是基于 库的。客户端代码在一个较高层上实现。我们需要另外一种稍微不同的方式来将SSL添加到已存在的服务中:

    首先,对于服务器而言,可以通过像下面这样使用一个mixin类来添加SSL:

    为了使用这个mixin类,你可以将它跟其他服务器类混合。例如,下面是定义一个基于SSL的XML-RPC服务器例子:

    1. # XML-RPC server with SSL
    2.  
    3. from xmlrpc.server import SimpleXMLRPCServer
    4.  
    5. class SSLSimpleXMLRPCServer(SSLMixin, SimpleXMLRPCServer):
    6. pass
    7.  
    8. Here's the XML-RPC server from Recipe 11.6 modified only slightly to use SSL:
    9.  
    10. import ssl
    11. from xmlrpc.server import SimpleXMLRPCServer
    12. from sslmixin import SSLMixin
    13.  
    14. class SSLSimpleXMLRPCServer(SSLMixin, SimpleXMLRPCServer):
    15. pass
    16.  
    17. _rpc_methods_ = ['get', 'set', 'delete', 'exists', 'keys']
    18. def __init__(self, *args, **kwargs):
    19. self._data = {}
    20. self._serv = SSLSimpleXMLRPCServer(*args, allow_none=True, **kwargs)
    21. for name in self._rpc_methods_:
    22. self._serv.register_function(getattr(self, name))
    23.  
    24. def get(self, name):
    25. return self._data[name]
    26.  
    27. def set(self, name, value):
    28. self._data[name] = value
    29.  
    30. def delete(self, name):
    31. del self._data[name]
    32.  
    33. def exists(self, name):
    34. return name in self._data
    35.  
    36. def keys(self):
    37. return list(self._data)
    38.  
    39. def serve_forever(self):
    40. self._serv.serve_forever()
    41.  
    42. if __name__ == '__main__':
    43. KEYFILE='server_key.pem' # Private key of the server
    44. CERTFILE='server_cert.pem' # Server certificate
    45. kvserv = KeyValueServer(('', 15000),
    46. keyfile=KEYFILE,
    47. certfile=CERTFILE)

    对于SSL客户端来讲一个比较复杂的问题是如何确认服务器证书或为服务器提供客户端认证(比如客户端证书)。不幸的是,暂时还没有一个标准方法来解决这个问题,需要自己去研究。不过,下面给出一个例子,用来建立一个安全的XML-RPC连接来确认服务器证书:

    1. from xmlrpc.client import SafeTransport, ServerProxy
    2. import ssl
    3.  
    4. class VerifyCertSafeTransport(SafeTransport):
    5. def __init__(self, cafile, certfile=None, keyfile=None):
    6. SafeTransport.__init__(self)
    7. self._ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
    8. self._ssl_context.load_verify_locations(cafile)
    9. if certfile:
    10. self._ssl_context.load_cert_chain(certfile, keyfile)
    11. self._ssl_context.verify_mode = ssl.CERT_REQUIRED
    12.  
    13. def make_connection(self, host):
    14. # Items in the passed dictionary are passed as keyword
    15. # arguments to the http.client.HTTPSConnection() constructor.
    16. # The context argument allows an ssl.SSLContext instance to
    17. # be passed with information about the SSL configuration
    18. s = super().make_connection((host, {'context': self._ssl_context}))
    19.  
    20. return s
    21.  
    22. # Create the client proxy
    23. s = ServerProxy('https://localhost:15000',
    24. transport=VerifyCertSafeTransport('server_cert.pem'),
    25. allow_none=True)

    服务器将证书发送给客户端,客户端来确认它的合法性。这种确认可以是相互的。如果服务器想要确认客户端,可以将服务器启动代码修改如下:

    为了让XML-RPC客户端发送证书,修改 ServerProxy 的初始化代码如下:

    1. # Create the client proxy
    2. s = ServerProxy('https://localhost:15000',
    3. transport=VerifyCertSafeTransport('server_cert.pem',
    4. 'client_cert.pem',
    5. 'client_key.pem'),
    6. allow_none=True)

    试着去运行本节的代码能测试你的系统配置能力和理解SSL。可能最大的挑战是如何一步步的获取初始配置key、证书和其他所需依赖。

    我解释下到底需要啥,每一个SSL连接终端一般都会有一个私钥和一个签名证书文件。这个证书包含了公钥并在每一次连接的时候都会发送给对方。对于公共服务器,它们的证书通常是被权威证书机构比如Verisign、Equifax或其他类似机构(需要付费的)签名过的。为了确认服务器签名,客户端回保存一份包含了信任授权机构的证书列表文件。例如,web浏览器保存了主要的认证机构的证书,并使用它来为每一个HTTPS连接确认证书的合法性。对本小节示例而言,只是为了测试,我们可以创建自签名的证书,下面是主要步骤:

    —–BEGIN RSA PRIVATE KEY—–MIICXQIBAAKBgQCZrCNLoEyAKF+f9UNcFaz5Osa6jf7qkbUl8si5xQrY3ZYC7juunL1dZLn/VbEFIITaUOgvBtPv1qUWTJGwga62VSG1oFE0ODIx3g2Nh4sRf+rySsx2L4442nx0z4O5vJQ7k6eRNHAZUUnCL50+YvjyLyt7ryLSjSuKhCcJsbZgPwIDAQABAoGAB5evrr7eyL4160tM5rHTeATlaLY3UBOe5Z8XN8Z6gLiB/ucSX9AysviVD/6F3oD6z2aL8jbeJc1vHqjt0dC2dwwm32vVl8mRdyoAsQpWmiqXrkvP4Bsl04VpBeHwQt8xNSW9SFhceL3LEvw9M8i9MV39viih1ILyH8OuHdvJyFECQQDLEjl2d2ppxND9PoLqVFAirDfX2JnLTdWbc+M11a9Jdn3hKF8TcxfEnFVs5Gav1MusicY5KB0ylYPbYbTvqKc7AkEAwbnRBO2VYEZsJZp2X0IZqP9ovWokkpYx+PE4+c6MySDgaMcigL7vWDIHJG1CHudD09GbqENasDzyb2HAIW4CzQJBAKDdkv+xoW6gJx42Auc2WzTcUHCAeXR/+BLpPrhKykzbvOQ8YvS5W764SUO1u1LWs3G+wnRMvrRvlMCZKgggBjkCQQCGJewto2+a+WkOKQXrNNScCDE5aPTmZQc5waCYq4UmCZQcOjkUOiN3ST1U5iuxRqfbV/yX6fw0qh+fLWtkOs/JAkA+okMSxZwqRtfgOFGBfwQ8/iKrnizeanTQ3L6scFXICHZXdJ3XQ6qUmNxNn7iJ7S/LDawo1QfWkCfD9FYoxBlg—–END RSA PRIVATE KEY—–

    服务器证书文件server_cert.pem内容类似下面这样:

    在服务器端代码中,私钥和证书文件会被传给SSL相关的包装函数。证书来自于客户端,私钥应该在保存在服务器中,并加以安全保护。

    在客户端代码中,需要保存一个合法证书授权文件来确认服务器证书。如果你没有这个文件,你可以在客户端复制一份服务器的证书并使用它来确认。连接建立后,服务器会提供它的证书,然后你就能使用已经保存的证书来确认它是否正确。

    服务器也能选择是否要确认客户端的身份。如果要这样做的话,客户端需要有自己的私钥和认证文件。服务器也需要保存一个被信任证书授权文件来确认客户端证书。

    如果你要在真实环境中为你的网络服务加上SSL的支持,这小节只是一个入门介绍而已。你还应该参考其他的文档,做好花费不少时间来测试它正常工作的准备。反正,就是得慢慢折腾吧~ ^_^