From 6b76a3b930b794b5c43bed3b27cb0b0f65b96d08 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Wed, 31 Jul 2024 13:15:30 +0100 Subject: [PATCH] pv-runtime: Add more robust mechanism to take CA certificates from the host This is a redo of commit d6a519f5 "pv-runtime: Add a mechanism to take custom CA certificates from the host" which aims to be more robust against host operating systems that might not have the Debian-compatible layout that we expect. This reverts commit 9e3d9c85a6fb855993a8e505d1bce474797bd444. steamrt/tasks#416 Signed-off-by: Simon McVittie <smcv@collabora.com> --- pressure-vessel/runtime.c | 126 +++++++++++++++++++++++++++++++++ pressure-vessel/runtime.h | 4 ++ pressure-vessel/wrap-context.c | 3 + pressure-vessel/wrap-context.h | 1 + pressure-vessel/wrap.c | 3 + 5 files changed, 137 insertions(+) diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c index ed51f95fc..7f3ee72e6 100644 --- a/pressure-vessel/runtime.c +++ b/pressure-vessel/runtime.c @@ -3062,6 +3062,118 @@ bind_gfx_provider (PvRuntime *self, return TRUE; } +static FlatpakBwrap * +pv_runtime_import_ca_certs (PvRuntime *self, + GError **error) +{ + static const char ca_path[] = "/etc/ssl/certs"; + static const char * const required_names[] = + { + /* /etc/ssl/certs/ca-certificates.crt is assumed to be an + * OpenSSL-compatible CAfile (concatenation of all trusted root certs), + * also used by other TLS libraries like GNUTLS */ + "ca-certificates.crt", + /* /etc/ssl/certs/ is assumed to be an OpenSSL-compatible CApath + * (one file per trusted root cert with names based on a truncated + * hash), mainly only used by OpenSSL. This is the hash for + * "ISRG Root X1", the root CA behind Let's Encrypt, which happens + * to be the CA used to sign repo.steampowered.com at the time + * of writing... */ + "4042bcee.0", + /* ... and this is the hash for "DigiCert High Assurance EV Root CA" + * which happens to be the CA used to sign store.steampowered.com. + * If both are present, then we assume all the other common CAs + * are too. */ + "244b5494.0", + /* Get these hashes from: + * openssl x509 -noout -subject_hash -in /path/to/cert.crt */ + }; + gboolean found[G_N_ELEMENTS (required_names)] = { FALSE }; + g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env); + g_auto(SrtDirIter) iter = SRT_DIR_ITER_CLEARED; + glnx_autofd int dirfd = -1; + struct dirent *dent; + size_t i; + + flatpak_bwrap_add_args (bwrap, + "--tmpfs", ca_path, + NULL); + + /* This is a developer-facing rather than end-user-facing flag, so + * for simplicity this assumes that the runtime is Debian-based, and + * that the host OS has also been set up to be compatible with + * Debian's layout for CA certificates (like Arch is). */ + dirfd = _srt_sysroot_open (self->host_root, + ca_path, + (SRT_RESOLVE_FLAGS_MUST_BE_DIRECTORY + | SRT_RESOLVE_FLAGS_READABLE), + NULL, + error); + + if (dirfd < 0) + return NULL; + + /* Check that we have a minimal Debian-compatible layout. If we don't, + * we'll just fail and the caller will have to deal with that. */ + + if (!_srt_dir_iter_init_take_fd (&iter, &dirfd, + SRT_DIR_ITER_FLAGS_NONE, + self->arbitrary_dirent_order, + error)) + return NULL; + + while (_srt_dir_iter_next_dent (&iter, &dent, NULL, NULL) && dent != NULL) + { + const char *member = dent->d_name; + + if (g_str_equal (member, "ca-certificates.crt") + || g_str_has_suffix (member, ".0")) + { + g_autoptr(GError) local_error = NULL; + g_autofree gchar *logical_path = NULL; + g_autofree gchar *resolved = NULL; + glnx_autofd int fd = -1; + + logical_path = g_build_filename (ca_path, member, NULL); + fd = _srt_sysroot_open (self->host_root, + logical_path, + (SRT_RESOLVE_FLAGS_READABLE + | SRT_RESOLVE_FLAGS_MUST_BE_REGULAR + | SRT_RESOLVE_FLAGS_RETURN_ABSOLUTE), + &resolved, + &local_error); + + if (fd < 0) + { + g_warning ("%s", local_error->message); + continue; + } + + if (!pv_runtime_bind_into_container (self, bwrap, + resolved, NULL, 0, + logical_path, + PV_RUNTIME_EMULATION_ROOTS_BOTH, + error)) + return NULL; + + for (i = 0; i < G_N_ELEMENTS (required_names); i++) + { + if (g_str_equal (member, required_names[i])) + found[i] = TRUE; + } + } + } + + for (i = 0; i < G_N_ELEMENTS (required_names); i++) + { + if (!found[i]) + return glnx_null_throw (error, "Required filename %s/%s not found", + ca_path, required_names[i]); + } + + return g_steal_pointer (&bwrap); +} + static gboolean bind_runtime_base (PvRuntime *self, FlatpakExports *exports, @@ -3425,6 +3537,20 @@ bind_runtime_base (PvRuntime *self, } } + if (self->flags & PV_RUNTIME_FLAGS_IMPORT_CA_CERTS) + { + g_autoptr(FlatpakBwrap) ca_args = NULL; + g_autoptr(GError) local_error = NULL; + + ca_args = pv_runtime_import_ca_certs (self, &local_error); + + if (ca_args != NULL) + flatpak_bwrap_append_bwrap (bwrap, ca_args); + else + g_warning ("Not importing host CA certificates: %s", + local_error->message); + } + return TRUE; } diff --git a/pressure-vessel/runtime.h b/pressure-vessel/runtime.h index 2ac21d8ee..6af7e7255 100644 --- a/pressure-vessel/runtime.h +++ b/pressure-vessel/runtime.h @@ -50,6 +50,8 @@ * root filesystem overlay for an interpreter like FEX-Emu * @PV_RUNTIME_FLAGS_DETERMINISTIC: Try harder to achieve deterministic * order, even where it shouldn't matter functionally + * @PV_RUNTIME_FLAGS_IMPORT_CA_CERTS: Try to import CA certificates from + * the host system, which is assumed to be Debian-compatible * @PV_RUNTIME_FLAGS_NONE: None of the above * * Flags affecting how we set up the runtime. @@ -66,6 +68,7 @@ typedef enum PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX = (1 << 7), PV_RUNTIME_FLAGS_INTERPRETER_ROOT = (1 << 8), PV_RUNTIME_FLAGS_DETERMINISTIC = (1 << 9), + PV_RUNTIME_FLAGS_IMPORT_CA_CERTS = (1 << 10), PV_RUNTIME_FLAGS_NONE = 0 } PvRuntimeFlags; @@ -86,6 +89,7 @@ typedef enum | PV_RUNTIME_FLAGS_FLATPAK_SUBSANDBOX \ | PV_RUNTIME_FLAGS_INTERPRETER_ROOT \ | PV_RUNTIME_FLAGS_DETERMINISTIC \ + | PV_RUNTIME_FLAGS_IMPORT_CA_CERTS \ ) typedef enum diff --git a/pressure-vessel/wrap-context.c b/pressure-vessel/wrap-context.c index 6e83c03bb..3b2621576 100644 --- a/pressure-vessel/wrap-context.c +++ b/pressure-vessel/wrap-context.c @@ -56,6 +56,7 @@ pv_wrap_options_init (PvWrapOptions *self) self->gc_runtimes = TRUE; self->generate_locales = TRUE; self->graphics_provider = FALSE; + self->import_ca_certs = FALSE; self->import_vulkan_layers = TRUE; self->launcher = FALSE; self->only_prepare = FALSE; @@ -537,6 +538,8 @@ pv_wrap_options_parse_environment (PvWrapOptions *self, self->remove_game_overlay); self->systemd_scope = _srt_boolean_environment ("PRESSURE_VESSEL_SYSTEMD_SCOPE", self->systemd_scope); + self->import_ca_certs = _srt_boolean_environment ("PRESSURE_VESSEL_IMPORT_CA_CERTS", + self->import_ca_certs); self->import_vulkan_layers = _srt_boolean_environment ("PRESSURE_VESSEL_IMPORT_VULKAN_LAYERS", self->import_vulkan_layers); diff --git a/pressure-vessel/wrap-context.h b/pressure-vessel/wrap-context.h index 644194da3..56d5bcfd7 100644 --- a/pressure-vessel/wrap-context.h +++ b/pressure-vessel/wrap-context.h @@ -79,6 +79,7 @@ typedef struct gboolean devel; gboolean gc_runtimes; gboolean generate_locales; + gboolean import_ca_certs; gboolean import_vulkan_layers; gboolean launcher; gboolean only_prepare; diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c index 1411570e6..8657f904c 100644 --- a/pressure-vessel/wrap.c +++ b/pressure-vessel/wrap.c @@ -629,6 +629,9 @@ main (int argc, if (_srt_util_is_debugging ()) flags |= PV_RUNTIME_FLAGS_VERBOSE; + if (self->options.import_ca_certs) + flags |= PV_RUNTIME_FLAGS_IMPORT_CA_CERTS; + if (self->options.import_vulkan_layers) flags |= PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS; -- GitLab