mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Merge pull request #41 from yusufipk/system-certificates
Look up the machine's certificates rather than the build machine's
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