mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Look up the machine's certificates rather than the build machine's
A build carries the OpenSSL of the machine it was built on, and that OpenSSL has one directory compiled into it as the only place it looks for certificates. For an AppImage built on Ubuntu that is /usr/lib/ssl, which Arch, Fedora and openSUSE do not have, so on any of them every HTTPS request fails with CERTIFICATE_VERIFY_FAILED: transcription and cleanup report it as a rejected key, and the model downloads fail too. Ask the machine instead, from the list curl and Go use, and only when the build's own answer turns out not to exist. The store on the machine rather than a copy carried along, because a copy goes stale as roots are rotated and would ignore a certificate somebody added themselves. Anybody who has already set SSL_CERT_FILE is left alone.
This commit is contained in:
@@ -14,6 +14,12 @@ Everything is written from the path Dikte is running as, which is why it is
|
||||
also run again on every start rather than once: an AppImage that was moved out
|
||||
of ~/Downloads leaves behind a menu entry naming a file that is no longer
|
||||
there, and the run after the move is the only moment that can be noticed.
|
||||
|
||||
The rest of the module is the other half of the same meeting. A build carries
|
||||
the libraries and the OpenSSL of the machine it was built on, and both of them
|
||||
have to be reconciled with the machine it is running on before anything else
|
||||
happens: what it hands to the programs it starts, and where it looks for the
|
||||
certificates that say who it is talking to.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -89,6 +95,53 @@ def restore_library_path():
|
||||
return moved
|
||||
|
||||
|
||||
# Where the distributions keep the trust store. One list rather than a guess
|
||||
# per distribution, in the order curl and Go try them.
|
||||
CA_FILES = (
|
||||
"/etc/ssl/certs/ca-certificates.crt", # Debian, Ubuntu, Arch, Gentoo
|
||||
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora, RHEL
|
||||
"/etc/ssl/ca-bundle.pem", # openSUSE
|
||||
"/etc/ssl/cert.pem", # Alpine, and macOS
|
||||
)
|
||||
CA_DIRECTORIES = ("/etc/ssl/certs", "/etc/pki/tls/certs")
|
||||
|
||||
|
||||
def use_system_certificates():
|
||||
"""Point the OpenSSL in this build at the machine's trust store. What it found.
|
||||
|
||||
A build carries the OpenSSL of the machine it was built on, and that
|
||||
OpenSSL has one directory compiled into it as the only place it will look:
|
||||
/usr/lib/ssl for an AppImage built on Ubuntu, which does not exist on Arch,
|
||||
Fedora or openSUSE. Every HTTPS request then fails with
|
||||
CERTIFICATE_VERIFY_FAILED, which reads like a rejected API key rather than
|
||||
a packaging fault, and takes the model downloads down with it.
|
||||
|
||||
The machine's own store rather than a copy carried along: a copy goes stale
|
||||
as roots are rotated, and it would ignore a certificate somebody added
|
||||
themselves, which is how a network that inspects its own traffic is made to
|
||||
work. Anybody who has already said where to look is not argued with.
|
||||
"""
|
||||
if not packaged():
|
||||
return None
|
||||
if os.environ.get("SSL_CERT_FILE") or os.environ.get("SSL_CERT_DIR"):
|
||||
return None
|
||||
|
||||
import ssl
|
||||
# Both of these are None unless the path they name is really there, so this
|
||||
# asks whether the build's idea of where certificates live survived the trip.
|
||||
defaults = ssl.get_default_verify_paths()
|
||||
if defaults.cafile or defaults.capath:
|
||||
return None
|
||||
|
||||
for name, candidates, exists in (("SSL_CERT_FILE", CA_FILES, os.path.isfile),
|
||||
("SSL_CERT_DIR", CA_DIRECTORIES, os.path.isdir)):
|
||||
for candidate in candidates:
|
||||
if exists(candidate):
|
||||
os.environ[name] = candidate
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def bundled_bin():
|
||||
"""Where a build keeps the helper programs it carries, if it carries any.
|
||||
|
||||
|
||||
+4
-2
@@ -7,8 +7,9 @@ The Finder hands a double-clicked application a -psn_0_… argument naming the
|
||||
process serial number, which argparse reads as a flag it has never heard of and
|
||||
exits over, and no one clicking an icon would ever find out why.
|
||||
|
||||
The two environment lines have to run before anything starts another process,
|
||||
and before is easier to be sure of here than anywhere further in.
|
||||
The three environment lines have to run before anything starts a process,
|
||||
opens a connection or reaches for ffmpeg, and before is easier to be sure of
|
||||
here than anywhere further in.
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -18,6 +19,7 @@ from dikte.app import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
integrate.restore_library_path()
|
||||
integrate.use_system_certificates()
|
||||
integrate.add_bundled_tools()
|
||||
sys.argv[1:] = [arg for arg in sys.argv[1:] if not arg.startswith("-psn_")]
|
||||
sys.exit(main())
|
||||
|
||||
@@ -158,6 +158,61 @@ class LibraryPath(unittest.TestCase):
|
||||
self.assertFalse(integrate.restore_library_path())
|
||||
|
||||
|
||||
class Certificates(unittest.TestCase):
|
||||
"""Where a build looks for the certificates that say who it is talking to.
|
||||
|
||||
An AppImage built on Ubuntu carries an OpenSSL with /usr/lib/ssl compiled
|
||||
into it, and Arch, Fedora and openSUSE have no such directory. Left alone
|
||||
it is every HTTPS request failing at once, reported as a rejected key.
|
||||
"""
|
||||
|
||||
def paths(self, cafile=None, capath=None):
|
||||
"""ssl.get_default_verify_paths(), which reports only what really exists."""
|
||||
import ssl
|
||||
return mock.patch("ssl.get_default_verify_paths",
|
||||
return_value=ssl.DefaultVerifyPaths(
|
||||
cafile, capath, "SSL_CERT_FILE", "/usr/lib/ssl/cert.pem",
|
||||
"SSL_CERT_DIR", "/usr/lib/ssl/certs"))
|
||||
|
||||
def test_a_store_the_build_cannot_find_is_looked_up(self):
|
||||
with Frozen("/tmp/.mount_x/usr/bin/dikte"), \
|
||||
mock.patch.dict(os.environ, {}, clear=True), \
|
||||
self.paths(), \
|
||||
mock.patch("os.path.isfile", lambda p: p == "/etc/ssl/cert.pem"):
|
||||
self.assertEqual(integrate.use_system_certificates(), "/etc/ssl/cert.pem")
|
||||
self.assertEqual(os.environ["SSL_CERT_FILE"], "/etc/ssl/cert.pem")
|
||||
|
||||
def test_a_directory_will_do_when_no_bundle_is_there(self):
|
||||
with Frozen("/tmp/.mount_x/usr/bin/dikte"), \
|
||||
mock.patch.dict(os.environ, {}, clear=True), \
|
||||
self.paths(), \
|
||||
mock.patch("os.path.isfile", return_value=False), \
|
||||
mock.patch("os.path.isdir", lambda p: p == "/etc/ssl/certs"):
|
||||
self.assertEqual(integrate.use_system_certificates(), "/etc/ssl/certs")
|
||||
self.assertEqual(os.environ["SSL_CERT_DIR"], "/etc/ssl/certs")
|
||||
|
||||
def test_a_build_that_can_already_find_them_is_left_alone(self):
|
||||
"""Which is the AppImage running on the distribution it was built on."""
|
||||
with Frozen("/tmp/.mount_x/usr/bin/dikte"), \
|
||||
mock.patch.dict(os.environ, {}, clear=True), \
|
||||
self.paths(capath="/usr/lib/ssl/certs"):
|
||||
self.assertIsNone(integrate.use_system_certificates())
|
||||
self.assertNotIn("SSL_CERT_FILE", os.environ)
|
||||
|
||||
def test_somebody_who_has_said_where_is_not_argued_with(self):
|
||||
"""A network that inspects its own traffic is made to work this way."""
|
||||
with Frozen("/tmp/.mount_x/usr/bin/dikte"), \
|
||||
mock.patch.dict(os.environ, {"SSL_CERT_FILE": "/opt/work/ca.pem"},
|
||||
clear=True), \
|
||||
self.paths():
|
||||
self.assertIsNone(integrate.use_system_certificates())
|
||||
self.assertEqual(os.environ["SSL_CERT_FILE"], "/opt/work/ca.pem")
|
||||
|
||||
def test_a_checkout_uses_the_python_it_was_installed_against(self):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
self.assertIsNone(integrate.use_system_certificates())
|
||||
|
||||
|
||||
class Linux(Home):
|
||||
def install(self, appimage, force=False):
|
||||
with Frozen("/tmp/.mount_x/usr/bin/dikte", appimage=str(appimage),
|
||||
|
||||
Reference in New Issue
Block a user