From d6a519f5590462892233a443b8628acb452cb713 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Tue, 19 Mar 2024 19:21:53 +0000
Subject: [PATCH] pv-runtime: Add a mechanism to take custom CA certificates
 from the host

This is intended to be a developer-only feature, so it makes some
assumptions about the host system: we assume that there is a
Debian-compatible TLS certificate store (as seen on e.g. Debian and Arch)
with a CAfile at /etc/ssl/certs/ca-certificates.crt and a
CApath at /etc/ssl/certs. There is no attempt to make this as robust
as we would usually want for an end-user-facing feature, and it will
fail hard (exit with an error) rather than trying to continue in the
face of adversity.

steamrt/tasks#416

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 pressure-vessel/runtime.c      | 66 ++++++++++++++++++++++++++++++++++
 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, 77 insertions(+)

diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c
index aa05c9e24..b52222c56 100644
--- a/pressure-vessel/runtime.c
+++ b/pressure-vessel/runtime.c
@@ -3425,6 +3425,72 @@ bind_runtime_base (PvRuntime *self,
         }
     }
 
+  if (self->flags & PV_RUNTIME_FLAGS_IMPORT_CA_CERTS)
+    {
+      g_auto(SrtDirIter) iter = SRT_DIR_ITER_CLEARED;
+      glnx_autofd int dirfd = -1;
+      struct dirent *dent;
+
+      flatpak_bwrap_add_args (bwrap,
+                              "--tmpfs", "/etc/ssl/certs",
+                              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,
+                                 "/etc/ssl/certs",
+                                 (SRT_RESOLVE_FLAGS_MUST_BE_DIRECTORY
+                                  | SRT_RESOLVE_FLAGS_READABLE),
+                                 NULL,
+                                 error);
+
+      if (dirfd < 0)
+        return FALSE;
+
+      if (!_srt_dir_iter_init_take_fd (&iter, &dirfd,
+                                       SRT_DIR_ITER_FLAGS_NONE,
+                                       self->arbitrary_dirent_order,
+                                       error))
+        return FALSE;
+
+      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 ("/etc/ssl/certs", member, NULL);
+              fd = _srt_sysroot_open (self->host_root,
+                                      logical_path,
+                                      (SRT_RESOLVE_FLAGS_READABLE
+                                       | 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 FALSE;
+            }
+        }
+    }
+
   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