New submission from Christian Korneck <christ...@korneck.de>:

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

----------
assignee: christian.heimes
components: SSL, Windows
messages: 335708
nosy: chris-k, christian.heimes, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
status: open
title: ssl - tls verify on Windows 10 fails
versions: Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue36011>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to