classification
Title: Python SSL stack doesn't securely validate certificate (as client)
Type: security Stage:
Components: Library (Lib) Versions: Python 3.4, Python 3.3, Python 3.1, Python 3.2, Python 2.7, Python 2.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: Arfrever, Dan.Kaminsky, barry, christian.heimes, fweimer, jcea, naif, pitrou
Priority: normal Keywords:

Created on 2011-12-22 14:34 by naif, last changed 2013-08-15 20:19 by pitrou. This issue is now closed.

Messages (10)
msg150094 - (view) Author: naif (naif) Date: 2011-12-22 14:34
It has been noticed by the well known security researcher Dan Kaminsky (
http://dankaminsky.com/) that Python SSL binding doesn't securely validate a digital certificate while used.

There is a new "match_hostname"http://pypi.python.org/pypi/backports.ssl_match_hostname/ that doesn't implement all the required, standard SSL/TLS Client security checks that should be done.

Dan suggestion to properly implement implement default SSL/TLS Client security check is as follow:

===
Encryption without authentication offers little value; it is the canonical "secure in the absence of an attacker" state.  
Python's SSL/TLS code presently does not authenticate the connection by default.  

There are of course reasons for this:

1) Collecting and maintaining the appropriate SSL/TLS roots is difficult, assuming people are even connecting to globally trusted resources
2) Changing authentication policy silently threatens to break production apps

These are real problems that can't just be waved away.  
In the long run, a more scalable trust distribution system needs to be supported (DNSSEC, most likely) but the present state of affairs remain ugly.  

This is what I would recommend:

A) Integrate the Mozilla CA pack into Python, updating it with each security release.

B) Make certificate validation tristate.  B
y default, it merely emits to stderr an error similar to what happens if deprecated content is included.  
This is vaguely heretical but whatever.  
Then add a couple of API calls:
   a) ValidateCerts, a single call that enables the Mozilla CA pack
   b) AddCert, a single call that declares a particular cert as trusted
   c) AddRoot, a single call that declares a particular root as trusted
   d) DisableValidation, a single call that removes the error
C) Integrate a hooking mechanism to add or replace the certificate validation process.  
Please send this API the name of the host you're attempting to validate, and be sure to allow it to return "I don't know, try your normal validation procedure".

Be sure you include all the necessary checks, including:
A) Expiration
B) SAN/CN
C) Basic Constraints checking
D) Name Constraints

Possibly a future version of Python should _actually_ deprecate non-validating SSL/TLS, but certainly not a security patch.
Too high a risk of breakage.
===

It would be valuable to provide the default SSL/TLS Client verification exactly like Mozilla/Chrome/Curl/Wget does.
msg150118 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-22 22:45
> There is a new "match_hostname" that doesn't implement all the 
> required, standard SSL/TLS Client security checks that should be done.

Indeed, as the name indicates, it just checks the hostname.
Please detail what the other security checks are (bonus points if you provide a patch + tests).

> It has been noticed by the well known security researcher Dan Kaminsky

What's the URL for this?

> A) Integrate the Mozilla CA pack into Python, updating it with each
> security release.

I suggest you discuss this on python-dev:
http://mail.python.org/mailman/listinfo/python-dev
msg150137 - (view) Author: Dan Kaminsky (Dan.Kaminsky) Date: 2011-12-23 08:54
>> There is a new "match_hostname" that doesn't implement all the 
>> required, standard SSL/TLS Client security checks that should be done.

>Indeed, as the name indicates, it just checks the hostname.
>Please detail what the other security checks are (bonus points if you >provide a patch + tests).

You need to check expiration date of the cert in question, and I suppose invocation date as well.
You need to look at each of the CNs in the subject name, as well as each of the DNSname types in the SAN extension.
You *absolutely must* make sure that each of the intermediate certificates has Basic Constraints: CA set to True.  Otherwise a certificate for foo.com can sign for bar.com (this keeps happening).
You should support the Name Constraints extension, that allows certificates to sign for a subset of names.  Nobody really uses this, because reliability is so low though.


> > It has been noticed by the well known security researcher Dan Kaminsky

> What's the URL for this?

I'll see your URL and raise you a submitted bug report with recommendations.  It seems to get better results than posting random whining on a web page somewhere :)

> > A) Integrate the Mozilla CA pack into Python, updating it with each
> > security release.

> I suggest you discuss this on python-dev:
> http://mail.python.org/mailman/listinfo/python-dev

It's an ugly dependency, I know.  X.509 suffers from a "false coherence" design, in which a couple of parties actively work to make it look like it has a coherent trust model.  The best you can do is try to borrow/leverage the work of one of those parties.
msg150139 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-23 09:14
> You need to check expiration date of the cert in question, and I
> suppose invocation date as well.
> You need to look at each of the CNs in the subject name, as well as
> each of the DNSname types in the SAN extension.
> You *absolutely must* make sure that each of the intermediate
> certificates has Basic Constraints: CA set to True.  Otherwise a
> certificate for foo.com can sign for bar.com (this keeps happening).

I'm confident this is already done by OpenSSL (if requested by user,
which means using CERT_REQUIRED or CERT_OPTIONAL in Python's ssl module
- these map to OpenSSL's SSL_VERIFY_PEER).

I guess it would be easy to check this by providing an outdated
certificate - perhaps I'll give it a try.

> > > A) Integrate the Mozilla CA pack into Python, updating it with each
> > > security release.
> 
> > I suggest you discuss this on python-dev:
> > http://mail.python.org/mailman/listinfo/python-dev
> 
> It's an ugly dependency, I know.  X.509 suffers from a "false
> coherence" design, in which a couple of parties actively work to make
> it look like it has a coherent trust model.  The best you can do is
> try to borrow/leverage the work of one of those parties.

I suppose distributing CA certificates is a practical solution for the
user, *if* we are dedicated enough (e.g. release managers would have to
agree with the burden of tracking changes, and possibly making emergency
releases when a cert must be removed). That's the reason I suggest
asking on python-dev; I don't feel like making that decision alone.

That said, system OpenSSL builds on Linux (and perhaps OS X) should have
been compiled against a well-known system location of CA certificates
maintained by the OS vendor. In this case, you can simply use
SSLContext.set_default_verify_paths
(http://docs.python.org/dev/library/ssl.html#ssl.SSLContext.set_default_verify_paths )
That doesn't help under Windows, though (where we build OpenSSL
ourselves so that the ssl module can be bundled in installers).
msg150141 - (view) Author: Dan Kaminsky (Dan.Kaminsky) Date: 2011-12-23 09:26
On Fri, Dec 23, 2011 at 4:14 AM, Antoine Pitrou <report@bugs.python.org>wrote:

>
> Antoine Pitrou <pitrou@free.fr> added the comment:
>
> > You need to check expiration date of the cert in question, and I
> > suppose invocation date as well.
> > You need to look at each of the CNs in the subject name, as well as
> > each of the DNSname types in the SAN extension.
> > You *absolutely must* make sure that each of the intermediate
> > certificates has Basic Constraints: CA set to True.  Otherwise a
> > certificate for foo.com can sign for bar.com (this keeps happening).
>
> I'm confident this is already done by OpenSSL (if requested by user,
> which means using CERT_REQUIRED or CERT_OPTIONAL in Python's ssl module
> - these map to OpenSSL's SSL_VERIFY_PEER).
>
> I guess it would be easy to check this by providing an outdated
> certificate - perhaps I'll give it a try.
>

Be sure to support SAN.  People forget that, and the API makes it a pain in
the butt (the validator doesn't even know who you're validating for).

>
> > > > A) Integrate the Mozilla CA pack into Python, updating it with each
> > > > security release.
> >
> > > I suggest you discuss this on python-dev:
> > > http://mail.python.org/mailman/listinfo/python-dev
> >
> > It's an ugly dependency, I know.  X.509 suffers from a "false
> > coherence" design, in which a couple of parties actively work to make
> > it look like it has a coherent trust model.  The best you can do is
> > try to borrow/leverage the work of one of those parties.
>
> I suppose distributing CA certificates is a practical solution for the
> user, *if* we are dedicated enough (e.g. release managers would have to
> agree with the burden of tracking changes, and possibly making emergency
> releases when a cert must be removed). That's the reason I suggest
> asking on python-dev; I don't feel like making that decision alone.
>

The CA set doesn't change *often*, but it does shift from time to time.

The right thing would be to use the in-built cert set if and only if the
system certs couldn't be checked.

>
> That said, system OpenSSL builds on Linux (and perhaps OS X) should have
> been compiled against a well-known system location of CA certificates
> maintained by the OS vendor. In this case, you can simply use
> SSLContext.set_default_verify_paths
> (
> http://docs.python.org/dev/library/ssl.html#ssl.SSLContext.set_default_verify_paths)
> That doesn't help under Windows, though (where we build OpenSSL
> ourselves so that the ssl module can be bundled in installers).
>

Whatever you've got right now isn't good enough to either be on by default,
or warn by default.  I wouldn't even recommend warning if you didn't ship
with certs.

Technically, you could check the Windows certificate stores too, if you
wanted to write that code.

Before going to python-dev, what do you think is feasible,
implementation-wise?
msg150143 - (view) Author: naif (naif) Date: 2011-12-23 10:20
Hi all,

i added a ticket on setting up a default CA-store for Python, eliminating the need of CA-Store mainteinance:
http://bugs.python.org/issue13655

This feature is a pre-requisite to implement by default SSL/TLS Client secure certificate verification.
msg150144 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-23 10:22
> Be sure to support SAN.  People forget that, and the API makes it a pain in
> the butt (the validator doesn't even know who you're validating for).

Right, that's why we added the match_hostname() function. It knows about subjectAltName, except for raw IP addresses.
The tests for it can be found here:
http://hg.python.org/cpython/file/0466ee1816b1/Lib/test/test_ssl.py#l265

> Technically, you could check the Windows certificate stores too, if you
> wanted to write that code.

Well, I don't know how to interface them with OpenSSL.

> Before going to python-dev, what do you think is feasible,
> implementation-wise?

Technically, shipping certificates shouldn't be difficult. The final install location is defined at "./configure" time, so loading the certs shouldn't be a problem either.
Whether or not we enable them by default is a matter of policy. I think enabling them by default could be a nasty surprise for users who currently rely on a narrower set of trusted certs.

> The right thing would be to use the in-built cert set if and only if the
> system certs couldn't be checked.

That might not be easy. OpenSSL's SSL_CTX_set_default_verify_paths() deliberately doesn't report errors.
msg150146 - (view) Author: naif (naif) Date: 2011-12-23 10:36
mmmm looking at OpenSSL command line, there is the "verify" that does a lot of checks on it's own:
http://www.openssl.org/docs/apps/verify.html

Dan, do you think that this apps does all the "best practice" verificati or it's missing something?

Antoine, in case it's useful, do you think that it would be possible to have something exactly-like the OpenSSL verify command?
msg150148 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-23 10:43
> Antoine, in case it's useful, do you think that it would be possible
> to have something exactly-like the OpenSSL verify command?

Well, to quote the page you mentioned:
“The verify program uses the same functions as the internal SSL and
S/MIME verification, therefore this description applies to these verify
operations too.”

So these checks are exactly the ones performed when using CERT_OPTIONAL
or CERT_REQUIRED.
Note that it is cursorily mentioned (or hinted at) at
http://docs.python.org/dev/library/ssl.html#verifying-certificates
msg195282 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-08-15 20:19
I'm gonna close this entry, since there's no actual issue to fix in Python.
History
Date User Action Args
2013-08-15 20:19:54pitrousetstatus: open -> closed
resolution: not a bug
messages: + msg195282
2013-08-14 11:24:09christian.heimessetnosy: + christian.heimes
2013-06-04 05:43:54Arfreversetnosy: + Arfrever
2013-06-03 18:15:55barrysetnosy: + barry
2013-03-08 08:48:55fweimersetnosy: + fweimer
2011-12-23 16:53:52jceasetnosy: + jcea
2011-12-23 10:43:08pitrousetmessages: + msg150148
2011-12-23 10:36:53naifsetmessages: + msg150146
2011-12-23 10:22:45pitrousetmessages: + msg150144
2011-12-23 10:20:02naifsetmessages: + msg150143
2011-12-23 09:26:03Dan.Kaminskysetmessages: + msg150141
2011-12-23 09:14:53pitrousetmessages: + msg150139
2011-12-23 08:54:59Dan.Kaminskysetnosy: + Dan.Kaminsky
messages: + msg150137
2011-12-22 22:45:29pitrousetnosy: + pitrou
messages: + msg150118
2011-12-22 14:34:49naifcreate