[Python-Dev] Verification of SSL cert and hostname made easy

Guido van Rossum guido at python.org
Sat Nov 30 23:16:05 CET 2013


Sounds good.

Is another change for asyncio needed?

On Sat, Nov 30, 2013 at 1:54 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:

>
> On 1 Dec 2013 04:32, "Christian Heimes" <christian at python.org> wrote:
> >
> > Hi,
> >
> > Larry has granted me a special pardon to add an outstanding fix for SSL,
> > http://bugs.python.org/issue19509 . Right now most stdlib modules
> > (ftplib, imaplib, nntplib, poplib, smtplib) neither support server name
> > indication (SNI) nor check the subject name of the peer's certificate
> > properly. The second issue is a major loop-hole because it allows
> > man-in-the-middle attack despite CERT_REQUIRED.
> >
> > With CERT_REQUIRED OpenSSL verifies that the peer's certificate is
> > directly or indirectly signed by a trusted root certification authority.
> > With Python 3.4 the ssl module is able to use/load the system's trusted
> > root certs on all major systems (Linux, Mac, BSD, Windows). On Linux and
> > BSD it requires a properly configured system openssl to locate the root
> > certs. This usually works out of the box. On Mac Apple's openssl build
> > is able to use the keychain API of OSX. I have added code for Windows'
> > system store.
> >
> > SSL socket code usually looks like this:
> >
> >   context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
> >   context.verify_mode = ssl.CERT_REQUIRED
> >   # new, by default it loads certs trusted for Purpose.SERVER_AUTH
> >   context.load_default_certs()
> >
> >   sock = socket.create_connection(("example.net", 443))
> >   sslsock = context.wrap_socket(sock)
> >
> > SSLContext.wrap_socket() wraps an ordinary socket into a SSLSocket. With
> > verify_mode = CERT_REQUIRED OpenSSL ensures that the peer's SSL
> > certificate is signed by a trusted root CA. In this example one very
> > important step is missing. The peer may return *ANY* signed certificate
> > for *ANY* hostname. These lines do NOT check that the certificate's
> > information match "example.net". An attacker can use any arbitrary
> > certificate (e.g. for "www.evil.net"), get it signed and abuse it for
> > MitM attacks on "mail.python.org".
> > http://docs.python.org/3/library/ssl.html#ssl.match_hostname must be
> > used to verify the cert. It's easy to forget it...
> >
> >
> > I have thought about multiple ways to fix the issue. At first I added a
> > new argument "check_hostname" to all affected modules and implemented
> > the check manually. For every module I had to modify several places for
> > SSL and STARTTLS and add / change about 10 lines. The extra lines are
> > required to properly shutdown and close the connection when the cert
> > doesn't match the hostname. I don't like the solution because it's
> > tedious. Every 3rd party author has to copy the same code, too.
> >
> > Then I came up with a better solution:
> >
> >   context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
> >   context.verify_mode = ssl.CERT_REQUIRED
> >   context.load_default_certs()
> >   context.check_hostname = True  # <-- NEW
> >
> >   sock = socket.create_connection(("example.net", 443))
> >   # server_hostname is already used for SNI
> >   sslsock = context.wrap_socket(sock, server_hostname="example.net")
> >
> >
> > This fix requires only a new SSLContext attribute and a small
> > modification to SSLSocket.do_handshake():
> >
> >   if self.context.check_hostname:
> >       try:
> >           match_hostname(self.getpeercert(), self.server_hostname)
> >       except Exception:
> >           self.shutdown(_SHUT_RDWR)
> >           self.close()
> >           raise
> >
> >
> > Pros:
> >
> > * match_hostname() is done in one central place
> > * the cert is matched as early as possible
> > * no extra arguments for APIs, a context object is enough
> > * library developers just have to add server_hostname to get SNI and
> > hostname checks at the same time
> > * users of libraries can configure cert verification and checking on the
> > same object
> > * missing checks will not pass silently
> >
> > Cons:
> >
> > * Doesn't work with OpenSSL < 0.9.8f (released 2007) because older
> > versions lack SNI support. The ssl module raises an exception for
> > server_hostname if SNI is not supported.
> >
> >
> > The default settings for all stdlib modules will still be verify_mode =
> > CERT_NONE and check_hostname = False for maximum backward compatibility.
> > Python 3.4 comes with a new function ssl.create_default_context() that
> > returns a new context with best practice settings and loaded root CA
> > certs. The settings are TLS 1.0, no weak and insecure ciphers (no MD5,
> > no RC4), no compression (CRIME attack), CERT_REQUIRED and check_hostname
> > = True (for client side only).
> >
> > http://bugs.python.org/issue19509 has a working patch for ftplib.
> >
> > Comments?
>
> If Larry is OK with it as RM (and it sounds like he is), +1 from me as
> well.
>

-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20131130/1879d6cc/attachment.html>


More information about the Python-Dev mailing list