加载中...

11.10 在网络服务中加入SSL


问题

You want to implement a network service involving sockets where servers and clientsauthenticate themselves and encrypt the transmitted data using SSL.

解决方案

The ssl module provides support for adding SSL to low-level socket connections. Inparticular, the ssl.wrap_socket() function takes an existing socket and wraps an SSLlayer around it. For example, here’s an example of a simple echo server that presents aserver certificate to connecting clients:

from socket import socket, AF_INET, SOCK_STREAMimport ssl

KEYFILE = ‘server_key.pem' # Private key of the serverCERTFILE = ‘server_cert.pem' # Server certificate (given to client)

def echo_client(s):while True:
data = s.recv(8192)if data == b'‘:

break

s.send(data)

s.close()print(‘Connection closed')

def echo_server(address):
s = socket(AF_INET, SOCK_STREAM)s.bind(address)s.listen(1)

Wrap with an SSL layer requiring client certss_ssl = ssl.wrap_socket(s,

keyfile=KEYFILE,certfile=CERTFILE,server_side=True)

Wait for connectionswhile True:

try:c,a = s_ssl.accept()print(‘Got connection', c, a)echo_client(c)except Exception as e:print(‘{}: {}'.format(e.class.name, e))

echo_server((‘', 20000))

Here’s an interactive session that shows how to connect to the server as a client. Theclient requires the server to present its certificate and verifies it:

>>> from socket import socket, AF_INET, SOCK_STREAM
>>> import ssl
>>> s = socket(AF_INET, SOCK_STREAM)
>>> s_ssl = ssl.wrap_socket(s,
...                         cert_reqs=ssl.CERT_REQUIRED,
...                         ca_certs = 'server_cert.pem')
>>> s_ssl.connect(('localhost', 20000))
>>> s_ssl.send(b'Hello World?')
12
>>> s_ssl.recv(8192)
b'Hello World?'
>>>

The problem with all of this low-level socket hacking is that it doesn’t play well withexisting network services already implemented in the standard library. For example,most server code (HTTP, XML-RPC, etc.) is actually based on the socketserver library.Client code is also implemented at a higher level. It is possible to add SSL to existingservices, but a slightly different approach is needed.First, for servers, SSL can be added through the use of a mixin class like this:

import ssl

class SSLMixin:
‘''Mixin class that adds support for SSL to existing servers basedon the socketserver module.‘''def init(self, *args,

keyfile=None, certfile=None, ca_certs=None,cert_reqs=ssl.NONE,**kwargs):

self._keyfile = keyfileself._certfile = certfileself._ca_certs = ca_certsself._cert_reqs = cert_reqssuper().init(*args, **kwargs)

def get_request(self):
client, addr = super().get_request()client_ssl = ssl.wrap_socket(client,

keyfile = self._keyfile,certfile = self._certfile,ca_certs = self._ca_certs,cert_reqs = self._cert_reqs,server_side = True)

return client_ssl, addr

To use this mixin class, you can mix it with other server classes. For example, here’s anexample of defining an XML-RPC server that operates over SSL:

XML-RPC server with SSL

from xmlrpc.server import SimpleXMLRPCServer

class SSLSimpleXMLRPCServer(SSLMixin, SimpleXMLRPCServer):pass
Here’s the XML-RPC server from Recipe 11.6 modified only slightly to use SSL:

import sslfrom xmlrpc.server import SimpleXMLRPCServerfrom sslmixin import SSLMixin

class SSLSimpleXMLRPCServer(SSLMixin, SimpleXMLRPCServer):passclass KeyValueServer:
_rpcmethods = [‘get', ‘set', ‘delete', ‘exists', ‘keys']def init(self, *args, **kwargs):

self._data = {}self._serv = SSLSimpleXMLRPCServer(*args, allow_none=True, **kwargs)for name in self._rpcmethods:

self._serv.register_function(getattr(self, name))

def get(self, name):return self._data[name]def set(self, name, value):self._data[name] = valuedef delete(self, name):del self._data[name]def exists(self, name):return name in self._datadef keys(self):return list(self._data)def serve_forever(self):self._serv.serve_forever()if name == ‘main':
KEYFILE='server_key.pem' # Private key of the serverCERTFILE='server_cert.pem' # Server certificatekvserv = KeyValueServer((‘', 15000),

keyfile=KEYFILE,certfile=CERTFILE),

kvserv.serve_forever()

To use this server, you can connect using the normal xmlrpc.client module. Just spec‐ify a https: in the URL. For example:

>>> from xmlrpc.client import ServerProxy
>>> s = ServerProxy('https://localhost:15000', allow_none=True)
>>> s.set('foo','bar')
>>> s.set('spam', [1, 2, 3])
>>> s.keys()
['spam', 'foo']
>>> s.get('foo')
'bar'
>>> s.get('spam')
[1, 2, 3]
>>> s.delete('spam')
>>> s.exists('spam')
False
>>>

One complicated issue with SSL clients is performing extra steps to verify the servercertificate or to present a server with client credentials (such as a client certificate).Unfortunately, there seems to be no standardized way to accomplish this, so research isoften required. However, here is an example of how to set up a secure XML-RPC con‐nection that verifies the server’s certificate:

from xmlrpc.client import SafeTransport, ServerProxyimport ssl

class VerifyCertSafeTransport(SafeTransport):def init(self, cafile, certfile=None, keyfile=None):
SafeTransport.init(self)self._ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)self._ssl_context.load_verify_locations(cafile)if cert:

self._ssl_context.load_cert_chain(certfile, keyfile)

self._ssl_context.verify_mode = ssl.CERT_REQUIRED

def make_connection(self, host):

Items in the passed dictionary are passed as keyword# arguments to the http.client.HTTPSConnection() constructor.# The context argument allows an ssl.SSLContext instance to# be passed with information about the SSL configurations = super().make_connection((host, {‘context': self._ssl_context}))

return s

Create the client proxys = ServerProxy(‘https://localhost:15000‘,

transport=VerifyCertSafeTransport(‘server_cert.pem'),allow_none=True)

As shown, the server presents a certificate to the client and the client verifies it. Thisverification can go both directions. If the server wants to verify the client, change theserver startup to the following:if name == ‘main':

KEYFILE='server_key.pem' # Private key of the serverCERTFILE='server_cert.pem' # Server certificateCA_CERTS='client_cert.pem' # Certificates of accepted clients

kvserv = KeyValueServer((‘', 15000),keyfile=KEYFILE,certfile=CERTFILE,ca_certs=CA_CERTS,cert_reqs=ssl.CERT_REQUIRED,)> kvserv.serve_forever()

To make the XML-RPC client present its certificates, change the ServerProxy initiali‐zation to this:

Create the client proxys = ServerProxy(‘https://localhost:15000‘,

transport=VerifyCertSafeTransport(‘server_cert.pem',‘client_cert.pem',‘client_key.pem'),> allow_none=True)

讨论

Getting this recipe to work will test your system configuration skills and understandingof SSL. Perhaps the biggest challenge is simply getting the initial configuration of keys,certificates, and other matters in order.To clarify what’s required, each endpoint of an SSL connection typically has a privatekey and a signed certificate file. The certificate file contains the public key and is pre‐sented to the remote peer on each connection. For public servers, certificates are nor‐mally signed by a certificate authority such as Verisign, Equifax, or similar organization(something that costs money). To verify server certificates, clients maintain a file con‐taining the certificates of trusted certificate authorities. For example, web browsersmaintain certificates corresponding to the major certificate authorities and use them toverify the integrity of certificates presented by web servers during HTTPS connections.For the purposes of this recipe, you can create what’s known as a self-signed certificate.Here’s how you do it:

bash % openssl req -new -x509 -days 365 -nodes -out server_cert.pem -keyout server_key.pem
Generating a 1024 bit RSA private key..........................................++++++...++++++

writing new private key to ‘server_key.pem'
You are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter ‘.', the field will be left blank.

Country Name (2 letter code) [AU]:USState or Province Name (full name) [Some-State]:IllinoisLocality Name (eg, city) []:ChicagoOrganization Name (eg, company) [Internet Widgits Pty Ltd]:Dabeaz, LLCOrganizational Unit Name (eg, section) []:Common Name (eg, YOUR name) []:localhostEmail Address []:bash %

When creating the certificate, the values for the various fields are often arbitrary. How‐ever, the “Common Name” field often contains the DNS hostname of servers. If you’rejust testing things out on your own machine, use “localhost.” Otherwise, use the domainname of the machine that’s going to run the server.As a result of this configuration, you will have a server_key.pem file that contains theprivate key. It looks like this:

—–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—–

The server certificate in server_cert.pem looks similar:

—–BEGIN CERTIFICATE—–MIIC+DCCAmGgAwIBAgIJAPMd+vi45js3MA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczEQMA4GA1UEBxMHQ2hpY2FnbzEUMBIGA1UEChMLRGFiZWF6LCBMTEMxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMzAxMTExODQyMjdaFw0xNDAxMTExODQyMjdaMFwxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczEQMA4GA1UEBxMHQ2hpY2FnbzEUMBIGA1UEChMLRGFiZWF6LCBMTEMxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmawjS6BMgChfn/VDXBWs+TrGuo3+6pG1JfLIucUK2N2WAu47rpy9XWS5/1WxBSCE2lDoLwbT79alFkyRsIGutlUhtaBRNDgyMd4NjYeLEX/q8krMdi+OONp8dM+DubyU

O5OnkTRwGVFJwi+dPmL48i8re68i0o0rioQnCbG2YD8CAwEAAaOBwTCBvjAdBgNVHQ4EFgQUrtoLHHgXiDZTr26NMmgKJLJLFtIwgY4GA1UdIwSBhjCBg4AUrtoLHHgXiDZTr26NMmgKJLJLFtKhYKReMFwxCzAJBgNVBAYTAlVTMREwDwYDVQQIEwhJbGxpbm9pczEQMA4GA1UEBxMHQ2hpY2FnbzEUMBIGA1UEChMLRGFiZWF6LCBMTEMxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAPMd+vi45js3MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAFci+dqvMG4xF8UTnbGVvZJPIzJDRee6Nbt6AHQo9pOdAIMAuWsGCplSOaDNdKKzl+b2UT2Zp3AIW4Qd51bouSNnR4M/gnr9ZD1ZctFd3jS+C5XRpD3vvcW5lAnCCC80P6rXy7d7hTeFu5EYKtRGXNvVNd/06NALGDflrrOwxF3Y=—–END CERTIFICATE—–

In server-related code, both the private key and certificate file will be presented to thevarious SSL-related wrapping functions. The certificate is what gets presented to clients.The private key should be protected and remains on the server.In client-related code, a special file of valid certificate authorities needs to be maintainedto verify the server’s certificate. If you have no such file, then at the very least, you canput a copy of the server’s certificate on the client machine and use that as a means forverification. During connection, the server will present its certificate, and then you’lluse the stored certificate you already have to verify that it’s correct.Servers can also elect to verify the identity of clients. To do that, clients need to havetheir own private key and certificate key. The server would also need to maintain a fileof trusted certificate authorities for verifying the client certificates.If you intend to add SSL support to a network service for real, this recipe really onlygives a small taste of how to set it up. You will definitely want to consult the documen‐tation for more of the finer points. Be prepared to spend a significant amount of timeexperimenting with it to get things to work.


还没有评论.