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