This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author chris-k
Recipients chris-k, christian.heimes, paul.moore, steve.dower, tim.golden, zach.ware
Date 2019-02-16.18:36:11
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1550342172.09.0.390948819907.issue36011@roundup.psfhosted.org>
In-reply-to
Content
Hello,

I have the impression that there's a general issue with how the Python stdlib module `ssl` uses the Windows certificate store to read the "bundle" of trusted Root CA certificates. At a first look, I couldn't find this issue documented elsewhere, so I'm trying to describe it below (apologies if its a duplicate). 

This issue leads to that on a standard Windows 10 installation with a standard Python 2.x or 3.x installation TLS verification for many webservers fails out of the box, including for common domains/webservers with a highly correct TLS setup like https://google.de or https://www.verisign.com/ .

In short: On a vanilla Win 10 with a vanilla Python 2/3 installation, HTTPS connections to "commonly trusted" domain names fail out of the box. Example with Python 2.7.15:

>>> import urllib2
>>> response = urllib2.urlopen("https://google.de")
[...]
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:726)

Expected Behavior: TLS verify succeeds
Actual Behavior: TLS verify fails

Affected Python version/environment: I believe every Python version that uses the Windows certificate store is affected (since 3.4 / 2.7.9). However, I've only tested 2.7.11, 2.7.15, 3.7.2 (all 64 bit). I did test on Windows 10 1803, 1809, Windows Server 2019 1809 (all Pro x64 with latest patchlevel, i.e. the Jan 2019 cumulative update). All tested Python versions on all tested Windows 10 versions show the same behavior.

--------

Details:

1.) Background

- Factor1: Python's "ssl" std lib
Since Python 3.4 / 2.7.9 the ssl lib uses the Windows certificate store to get a "bundle" of the trusted root CA certificates. (Some Python libraries like requests bring their own ca bundle though, usually through certifi. These libs are not affected). However, the ssl lib is not using the Windows SCHANNEL library but instead bundles its own copy of openssl.

- Factor2: Windows 10 behavior
Windows provides a certificate store, a vendor managed and updated "bundle" of Trusted Root CA certificates and a library for TLS operations called SCHANNEL (the native Windows openssl equivalent).

On Windows 10, the list of pre-installed Trusted Root CA certificates is very minimal. On Windows 10 1809 only 12 Root CAs are known by the certificate store. In comparison certifi (Mozilla cabundle) currently lists 134 trusted RootCAs. Many widely trusted RootCAs are missing out of the box in the Windows certstore. Instead there's an online download mechanism used by the SCHANNEL library to download additional trusted root CA certificates from a Microsoft server when they are needed for the first time.

Example: The certificate currently used for https://google.de was signed by an IntermediateCA which was signed by the RootCA "GlobalSign Root CA - R2". The cert for this RootCA is not out of the box present in the Windows certstore and therefore not trusted. When I make a HTTPS connection to this domain with a client that uses the SCHANNEL library (i.e. Microsoft Edge or Internet Explorer browser), the connection succeeds and is shown as "trusted". Afterwards the previously missing RootCA certificate appears in the windows certstore. (The Windows certstores can get inspected with the GUIs certml.msc (Machine store) and certmgr.msc (User store)).


2.) Behavior

- install a vanilla Windows 10 1809 with default settings
- install a vanilla Python 2.7.15 and/or 3.7.2

In Python:

c:\python27\python.exe
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket, ssl
>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS)
>>> context.verify_mode = ssl.CERT_REQUIRED
>>> context.check_hostname = True
# by default there are no cacerts in the context
>>> len(context.get_ca_certs())
0
>>> context.load_default_certs()
>>> len(context.get_ca_certs())
# after loading the cacerts from the Windows cert store "ROOT", we are seeing some - but it's only 12 root cacerts in a vanilla Windows 10 (compared to 134 in the certifi / mozilla cabundle!)
12
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> ssl_sock = context.wrap_socket(s, server_hostname='www.google.de')
>>> ssl_sock.connect(('www.google.de', 443))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\python27\lib\ssl.py", line 882, in connect
    self._real_connect(addr, False)
  File "c:\python27\lib\ssl.py", line 873, in _real_connect
    self.do_handshake()
  File "c:\python27\lib\ssl.py", line 846, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:726)
>>> ssl_sock.close()

This first attempt to make a HTTPS connection to https://google.de failed because the required RootCA for this domain is not part of the very minimal Windows out-of-the-box ca bundle.

Now let's make a https request against this domain with an application that uses the Windows SCHANNEL library (for example by typing https://google.de/ into the address bar in Internet Explorer / Edge browser). I will use the experimental pySchannelSSL here:

$ git clone https://github.com/lsowen/pySchannelSSL.git
$ "c:\Program Files\Python37\python.exe"
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
>>> import pySchannelSSL.httpshandler
>>> h = pySchannelSSL.httpshandler.SSLConnection("google.de", port=443)
>>> h.connect()
>>> h.close()

As part of processing the above request, the Windows SCHANNEL library has magically fetched the missing trusted RootCA certificate from a Microsoft server and has stored it permanently in the Windows "Trusted Root CAs" certstore.

We can verify this with:


>>> import socket, ssl
>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS)
>>> context.load_default_certs()
>>> len(context.get_ca_certs())
15


Note that in our first attempt there were only 12 root cacerts in the Windows certstore. Now it's 15. And the only difference is that in between we've made an SCHANNEL-based https connection to the google.de domain. (You can also see the additional root certificates via the certificates mmc consoles certlm.msc and certmgr.msc).

From now on all non-SCHANNEL based HTTPS connections via the Python 2/3 ssl standard lib work, as SCHANNEL has permanently placed the RootCA cert in the windows certstore:


c:\python27\python.exe
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket, ssl
>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS)
>>> context.verify_mode = ssl.CERT_REQUIRED
>>> context.check_hostname = True
>>> context.load_default_certs()
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> ssl_sock = context.wrap_socket(s, server_hostname='www.google.de')
# got a certificate verify failed error here in the first try, this time the verify is successfull
>>> ssl_sock.connect(('www.google.de', 443))
>>> ssl_sock.close()


3.) Conclusion
I believe the way how the Python "ssl" stdlib uses the Windows Certificate Store is not ideal. Windows seems to expects all TLS connections to be made through the SCHANNEL library. The Trusted Root CA store in Windows 10 only seems to function as some sort of cache for SCHANNEL but is not as a complete source of truth. Maybe letting the "ssl" stdlib make a minimal SCHANNEL call before handing over to openssl could provide a minimal invasive fix?

(Side note: I would still advocate for not bypassing the Windows certstore, as having a certstore per application is a security issue and big pain for deploying/updating own "Intranet" RootCA certificates).

--------


Best,
Chris
History
Date User Action Args
2019-02-16 18:36:12chris-ksetrecipients: + chris-k, paul.moore, christian.heimes, tim.golden, zach.ware, steve.dower
2019-02-16 18:36:12chris-ksetmessageid: <1550342172.09.0.390948819907.issue36011@roundup.psfhosted.org>
2019-02-16 18:36:12chris-klinkissue36011 messages
2019-02-16 18:36:11chris-kcreate