Chapter 6 ■ tLS/SSL
101
out-of-date. But surely, they think, you will want your own servers to insist on modern and secure ciphers! While the
settings chosen by create_default_context() will change with each new version of Python, here are some of the
choices that it makes under Python 3.4 to provide you with an illustration:
• Both your client and server will be willing to negotiate the TLS version that is spoken, thanks
to the fact that create_default_context() sets the protocol to PROTOCOL_SSLv23 when
creating your new SSLContext object.
• Both your client and server will refuse to speak the old protocols SSLv2 and SSLv3 because of
known weaknesses in each of them. Instead, they will insist that the peer to which they are
speaking use a dialect at least as recent as TLSv1. (The most common client that this choice
excludes is Internet Explorer 6 running on Windows XP—a combination so old that it is no
longer even officially supported by Microsoft).
• TLS compression is turned off because of attacks that it makes possible.
• Here is the first difference between the client and server settings. Since most TLS
conversations on the Internet involve a client without its own signed certificate (such as a
typical web browser) talking to a server that does possess a valid and signed certificate (such
as PyPI or Google or GitHub or your bank), Python tells the server not to even attempt peer
certificate validation (the context’s verify_mode is set to ssl.CERT_NONE), but it insists that a
client always validate the remote certificate and fail with an exception if it cannot
(ssl.CERT_REQUIRED).
• The other client-server difference is their choice of ciphers. The client settings support a larger
list of possible ciphers, even down to the old RC4 stream cipher. The server settings are much
stricter and strongly prefer modern ciphers that provide Perfect Forward Security (PFS) so
that a compromised server key—whether captured by criminals or released by court order—
cannot lead to old conversations being divulged.
It was easy to compile the previous list: I simply opened ssl.py in the Standard Library and read the source code
of create_default_context() to learn the choices it makes. You can do so yourself, especially as new Python versions
come out and the previous list begins to grow out-of-date. The ssl.py source code even includes the raw list of
ciphers for both client and server operations, currently named _DEFAULT_CIPHERS and _RESTRICTED_SERVER_CIPHERS,
if you are curious enough to want to review them. You can consult recent OpenSSL documentation to learn about
what the options in each string mean.
The cafile option provided when building the context in Listing 6-3 determines which certificate authorities
your script will be willing to trust when verifying a remote certificate. If its value is None, which is the default if you
choose not to specify the cafile keyword, then create_default_context() will automatically call the loaddefault
certs() method of your new context before returning it. This attempts to load all of the default CA certificates
that browsers on your operating system would trust when connecting to a remote site, and it should be sufficient
to validate public web sites and other services that have bought a certificate from a well-known public certificate
authority. If instead cafile is a string specifying a filename, then no certificates are loaded from the operating system,
and only CA certificates provided in that file will be trusted to validate the remote end of your TLS connection. (Note
that you can make both kinds of certificates available if you create the context with cafile set to None and then call
load_verify_locations() to install any further certificates.)
Finally, there are two crucial options that are provided to wrap_socket() in Listing 6-3—one for the server and
the other for the client. The server is given the option server_side=True simply because one of the two ends has to
assume the responsibilities of the server or the negotiation will fail with an error. The client call needs something
more specific: the name of the host to which you think you have connected with connect() so that it can be checked
against the subject fields of the certificate being proffered by the server. This extremely important check is performed
automatically, as long as you always provide the server_hostname keyword to wrap_socket(), as shown in the listing.