indygreg / PyOxidizer

A modern Python application packaging and distribution tool
Mozilla Public License 2.0
5.31k stars 227 forks source link

Cannot establish SSL connection on Fedora, default cert chain not found #283

Open layday opened 3 years ago

layday commented 3 years ago

(I assume this is more relevant to python-build-standalone but I'm reporting it here since I've experienced it with PyOxidizer.)

Using the low-level SSL and socket APIs, attempting to make an SSL connection on a fresh install of Fedora 32 fails with:

Traceback (most recent call last):
  File "test", line 11, in <module>
  File "ssl", line 423, in wrap_socket
  File "ssl", line 870, in _create
  File "ssl", line 1139, in do_handshake
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)

After some modest digging, I discovered that the CA paths differ between the Fedora-vendored Python and PyOx's own Python. This is the return value of __import__('ssl').get_default_verify_paths() with Fedora's Python:

DefaultVerifyPaths(cafile='/etc/pki/tls/cert.pem', capath='/etc/pki/tls/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/pki/tls/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/pki/tls/certs')

And this is PyOxidizer:

DefaultVerifyPaths(cafile=None, capath='/etc/ssl/certs', openssl_cafile_env='SSL_CERT_FILE', openssl_cafile='/etc/ssl/cert.pem', openssl_capath_env='SSL_CERT_DIR', openssl_capath='/etc/ssl/certs')

When creating a new SSL context, the SSL module calls set_default_verify_paths with the output of get_default_verify_paths. The PyOx openssl_cafile, /etc/ssl/cert.pem, does not exist, whatever that might be indicative of; /etc/ssl/certs is a symlink to /etc/pki/tls/certs. You can repro this by bundling:

import socket
import ssl

context = ssl.create_default_context()

print(ssl.get_default_verify_paths())

with socket.create_connection(('github.com', 443)) as sock:
    with context.wrap_socket(sock, server_hostname='github.com') as ssock:
        ssock.send(b'HEAD / HTTP/1.0\r\nHost: github.com\r\n\r\n')
        print(ssock.recv(64).decode() + '...')

In effect this means that you cannot make any HTTPS requests at all on Fedora out of the box using PyOx.

indygreg commented 3 years ago

There is a default search path for x509 certificates compiled into the OpenSSL/LibreSSL that is bundled with the Python distribution that PyOxidizer is using. This default search path likely only works on Debian[-derived] distributions. We'll need to teach the PyOxidizer runtime to locate and use the host machine's default x509 certificate store. We already do this for the terminfo database (https://github.com/indygreg/PyOxidizer/blob/24ac847868c22ae7a8517e3fd77dddd7d5c1c7b7/pyembed/src/interpreter.rs#L214). We'll likely employ a similar trick for x509 certificates.

lorengordon commented 1 year ago

Trying to build a package using pyoxidizer for the first time, and ran smack into this issue. While I can hardcode the Fedora paths, the package also needs to support Windows. Without pyoxidizer, this seems to work fine for all platforms:

context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)