lib/repo: Add ostree_repo_remote_get_gpg_keys()
authorDan Nicholson <nicholson@endlessm.com>
Tue, 13 Aug 2019 19:36:00 +0000 (13:36 -0600)
committerDan Nicholson <dbn@endlessos.org>
Thu, 15 Jul 2021 21:50:04 +0000 (15:50 -0600)
This function enumerates the trusted GPG keys for a remote and returns
an array of `GVariant`s describing them. This is useful to see which
keys are collected by ostree for a particular remote. The same
information can be gathered with `gpg`. However, since ostree allows
multiple keyring locations, that's only really useful if you have
knowledge of how ostree collects GPG keyrings.

The format of the variants is documented in
`OSTREE_GPG_KEY_GVARIANT_FORMAT`. This format is primarily a copy of
selected fields within `gpgme_key_t` and its subtypes. The fields are
placed within vardicts rather than using a more efficient tuple of
concrete types. This will allow flexibility if more components of
`gpgme_key_t` are desired in the future.

Makefile-libostree.am
apidoc/ostree-sections.txt
src/libostree/libostree-devel.sym
src/libostree/ostree-gpg-verifier.c
src/libostree/ostree-gpg-verifier.h
src/libostree/ostree-repo.c
src/libostree/ostree-repo.h

index dd396974064ae6c8107864bad34552bcc5b6e7e6..d40de48d13fe8c7cd9c5d4458ae0e60aa3a55e29 100644 (file)
@@ -173,9 +173,9 @@ endif # USE_GPGME
 symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
 
 # Uncomment this include when adding new development symbols.
-#if BUILDOPT_IS_DEVEL_BUILD
-#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
-#endif
+if BUILDOPT_IS_DEVEL_BUILD
+symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
+endif
 
 # http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
 wl_versionscript_arg = -Wl,--version-script=
index 2da1d749695c831d2475bae27b68a6bb3f273731..4d02755558b32b929b7e319c3f22eca35552d30f 100644 (file)
@@ -337,6 +337,7 @@ ostree_repo_remote_list_collection_refs
 ostree_repo_remote_get_url
 ostree_repo_remote_get_gpg_verify
 ostree_repo_remote_get_gpg_verify_summary
+ostree_repo_remote_get_gpg_keys
 ostree_repo_remote_gpg_import
 ostree_repo_remote_fetch_summary
 ostree_repo_remote_fetch_summary_with_options
@@ -482,6 +483,8 @@ ostree_repo_regenerate_summary
 OSTREE_REPO
 OSTREE_IS_REPO
 OSTREE_TYPE_REPO
+OSTREE_GPG_KEY_GVARIANT_STRING
+OSTREE_GPG_KEY_GVARIANT_FORMAT
 ostree_repo_get_type
 ostree_repo_commit_modifier_get_type
 ostree_repo_transaction_stats_get_type
index e3cd14a4afe56c8312e676914df80f6d08d329e0..75bc4647705fa311155348f38401c37df42b99ae 100644 (file)
    - uncomment the include in Makefile-libostree.am
 */
 
+LIBOSTREE_2021.4 {
+global:
+  ostree_repo_remote_get_gpg_keys;
+} LIBOSTREE_2021.3;
+
 /* Stub section for the stable release *after* this development one; don't
  * edit this other than to update the year.  This is just a copy/paste
  * source.  Replace $LASTSTABLE with the last stable version, and $NEWVERSION
index 88850d467eb8d07b2f353fa2c9755c7f6c07d68d..e9f5c5e332d336b8ba7caef03562a23ffed2e399 100644 (file)
@@ -185,6 +185,91 @@ _ostree_gpg_verifier_import_keys (OstreeGpgVerifier  *self,
   return ret;
 }
 
+gboolean
+_ostree_gpg_verifier_list_keys (OstreeGpgVerifier   *self,
+                                const char * const  *key_ids,
+                                GPtrArray          **out_keys,
+                                GCancellable        *cancellable,
+                                GError             **error)
+{
+  GLNX_AUTO_PREFIX_ERROR("GPG", error);
+  g_auto(gpgme_ctx_t) context = NULL;
+  g_autoptr(GOutputStream) pubring_stream = NULL;
+  g_autofree char *tmp_dir = NULL;
+  g_autoptr(GPtrArray) keys = NULL;
+  gpgme_error_t gpg_error = 0;
+  gboolean ret = FALSE;
+
+  if (g_cancellable_set_error_if_cancelled (cancellable, error))
+    goto out;
+
+  context = ot_gpgme_new_ctx (NULL, error);
+  if (context == NULL)
+    goto out;
+
+  if (!ot_gpgme_ctx_tmp_home_dir (context, &tmp_dir, &pubring_stream,
+                                  cancellable, error))
+    goto out;
+
+  if (!_ostree_gpg_verifier_import_keys (self, context, pubring_stream,
+                                         cancellable, error))
+    goto out;
+
+  keys = g_ptr_array_new_with_free_func ((GDestroyNotify) gpgme_key_unref);
+  if (key_ids != NULL)
+    {
+      for (guint i = 0; key_ids[i] != NULL; i++)
+        {
+          gpgme_key_t key = NULL;
+
+          gpg_error = gpgme_get_key (context, key_ids[i], &key, 0);
+          if (gpg_error != GPG_ERR_NO_ERROR)
+            {
+              ot_gpgme_throw (gpg_error, error, "Unable to find key \"%s\"",
+                              key_ids[i]);
+              goto out;
+            }
+
+          /* Transfer ownership. */
+          g_ptr_array_add (keys, key);
+        }
+    }
+  else
+    {
+      gpg_error = gpgme_op_keylist_start (context, NULL, 0);
+      while (gpg_error == GPG_ERR_NO_ERROR)
+        {
+          gpgme_key_t key = NULL;
+
+          gpg_error = gpgme_op_keylist_next (context, &key);
+          if (gpg_error != GPG_ERR_NO_ERROR)
+            break;
+
+          /* Transfer ownership. */
+          g_ptr_array_add (keys, key);
+        }
+
+      if (gpgme_err_code (gpg_error) != GPG_ERR_EOF)
+        {
+          ot_gpgme_throw (gpg_error, error, "Unable to list keys");
+          goto out;
+        }
+    }
+
+  if (out_keys != NULL)
+    *out_keys = g_steal_pointer (&keys);
+
+  ret = TRUE;
+
+ out:
+  if (tmp_dir != NULL) {
+    ot_gpgme_kill_agent (tmp_dir);
+    (void) glnx_shutil_rm_rf_at (AT_FDCWD, tmp_dir, NULL, NULL);
+  }
+
+  return ret;
+}
+
 OstreeGpgVerifyResult *
 _ostree_gpg_verifier_check_signature (OstreeGpgVerifier  *self,
                                       GBytes             *signed_data,
index 634d33b29971afcd3e519d387dde49f598989f86..3d803c4953a8f9d06327de00bda764eb219be8ab 100644 (file)
@@ -51,6 +51,12 @@ OstreeGpgVerifyResult *_ostree_gpg_verifier_check_signature (OstreeGpgVerifier *
                                                              GCancellable      *cancellable,
                                                              GError           **error);
 
+gboolean      _ostree_gpg_verifier_list_keys (OstreeGpgVerifier   *self,
+                                              const char * const  *key_ids,
+                                              GPtrArray          **out_keys,
+                                              GCancellable        *cancellable,
+                                              GError             **error);
+
 gboolean      _ostree_gpg_verifier_add_keyring_dir (OstreeGpgVerifier   *self,
                                                     GFile               *path,
                                                     GCancellable        *cancellable,
index 254f7010ef7a433b18aee366f24678230495dfc2..b20aa6179357699b8e6e54f185b20528c59952af 100644 (file)
@@ -2353,6 +2353,130 @@ out:
 #endif /* OSTREE_DISABLE_GPGME */
 }
 
+static gboolean
+_ostree_repo_gpg_prepare_verifier (OstreeRepo         *self,
+                                   const gchar        *remote_name,
+                                   GFile              *keyringdir,
+                                   GFile              *extra_keyring,
+                                   gboolean            add_global_keyrings,
+                                   OstreeGpgVerifier **out_verifier,
+                                   GCancellable       *cancellable,
+                                   GError            **error);
+
+/**
+ * ostree_repo_remote_get_gpg_keys:
+ * @self: an #OstreeRepo
+ * @name (nullable): name of the remote or %NULL
+ * @key_ids: (array zero-terminated=1) (element-type utf8) (nullable):
+ *    a %NULL-terminated array of GPG key IDs to include, or %NULL
+ * @out_keys: (out) (optional) (element-type GVariant) (transfer container):
+ *    return location for a #GPtrArray of the remote's trusted GPG keys, or
+ *    %NULL
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Enumerate the trusted GPG keys for the remote @name. If @name is
+ * %NULL, the global GPG keys will be returned. The keys will be
+ * returned in the @out_keys #GPtrArray. Each element in the array is a
+ * #GVariant of format %OSTREE_GPG_KEY_GVARIANT_FORMAT. The @key_ids
+ * array can be used to limit which keys are included. If @key_ids is
+ * %NULL, then all keys are included.
+ *
+ * Returns: %TRUE if the GPG keys could be enumerated, %FALSE otherwise
+ *
+ * Since: 2021.4
+ */
+gboolean
+ostree_repo_remote_get_gpg_keys (OstreeRepo          *self,
+                                 const char          *name,
+                                 const char * const  *key_ids,
+                                 GPtrArray          **out_keys,
+                                 GCancellable        *cancellable,
+                                 GError             **error)
+{
+#ifndef OSTREE_DISABLE_GPGME
+  g_autoptr(OstreeGpgVerifier) verifier = NULL;
+  gboolean global_keyrings = (name == NULL);
+  if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, global_keyrings,
+                                          &verifier, cancellable, error))
+    return FALSE;
+
+  g_autoptr(GPtrArray) gpg_keys = NULL;
+  if (!_ostree_gpg_verifier_list_keys (verifier, key_ids, &gpg_keys,
+                                       cancellable, error))
+    return FALSE;
+
+  g_autoptr(GPtrArray) keys =
+    g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
+  for (guint i = 0; i < gpg_keys->len; i++)
+    {
+      gpgme_key_t key = gpg_keys->pdata[i];
+
+      g_auto(GVariantBuilder) subkeys_builder = OT_VARIANT_BUILDER_INITIALIZER;
+      g_variant_builder_init (&subkeys_builder, G_VARIANT_TYPE ("a(a{sv})"));
+      g_auto(GVariantBuilder) uids_builder = OT_VARIANT_BUILDER_INITIALIZER;
+      g_variant_builder_init (&uids_builder, G_VARIANT_TYPE ("a(a{sv})"));
+      for (gpgme_subkey_t subkey = key->subkeys; subkey != NULL;
+           subkey = subkey->next)
+        {
+          g_auto(GVariantDict) subkey_dict = OT_VARIANT_BUILDER_INITIALIZER;
+          g_variant_dict_init (&subkey_dict, NULL);
+          g_variant_dict_insert_value (&subkey_dict, "fingerprint",
+                                       g_variant_new_string (subkey->fpr));
+          g_variant_dict_insert_value (&subkey_dict, "created",
+                                       g_variant_new_int64 (GINT64_TO_BE (subkey->timestamp)));
+          g_variant_dict_insert_value (&subkey_dict, "expires",
+                                       g_variant_new_int64 (GINT64_TO_BE (subkey->expires)));
+          g_variant_dict_insert_value (&subkey_dict, "revoked",
+                                       g_variant_new_boolean (subkey->revoked));
+          g_variant_dict_insert_value (&subkey_dict, "expired",
+                                       g_variant_new_boolean (subkey->expired));
+          g_variant_dict_insert_value (&subkey_dict, "invalid",
+                                       g_variant_new_boolean (subkey->invalid));
+          g_variant_builder_add (&subkeys_builder, "(@a{sv})",
+                                 g_variant_dict_end (&subkey_dict));
+        }
+
+      for (gpgme_user_id_t uid = key->uids; uid != NULL; uid = uid->next)
+        {
+          g_auto(GVariantDict) uid_dict = OT_VARIANT_BUILDER_INITIALIZER;
+          g_variant_dict_init (&uid_dict, NULL);
+          g_variant_dict_insert_value (&uid_dict, "uid",
+                                       g_variant_new_string (uid->uid));
+          g_variant_dict_insert_value (&uid_dict, "name",
+                                       g_variant_new_string (uid->name));
+          g_variant_dict_insert_value (&uid_dict, "comment",
+                                       g_variant_new_string (uid->comment));
+          g_variant_dict_insert_value (&uid_dict, "email",
+                                       g_variant_new_string (uid->email));
+          g_variant_dict_insert_value (&uid_dict, "revoked",
+                                       g_variant_new_boolean (uid->revoked));
+          g_variant_dict_insert_value (&uid_dict, "invalid",
+                                       g_variant_new_boolean (uid->invalid));
+          g_variant_builder_add (&uids_builder, "(@a{sv})",
+                                 g_variant_dict_end (&uid_dict));
+        }
+
+      /* Currently empty */
+      g_auto(GVariantDict) metadata_dict = OT_VARIANT_BUILDER_INITIALIZER;
+      g_variant_dict_init (&metadata_dict, NULL);
+
+      GVariant *key_variant = g_variant_new ("(@a(a{sv})@a(a{sv})@a{sv})",
+                                             g_variant_builder_end (&subkeys_builder),
+                                             g_variant_builder_end (&uids_builder),
+                                             g_variant_dict_end (&metadata_dict));
+      g_ptr_array_add (keys, g_variant_ref_sink (key_variant));
+    }
+
+  if (out_keys)
+    *out_keys = g_steal_pointer (&keys);
+
+  return TRUE;
+#else /* OSTREE_DISABLE_GPGME */
+  return glnx_throw (error, "GPG feature is disabled in a build time");
+#endif /* OSTREE_DISABLE_GPGME */
+}
+
 /**
  * ostree_repo_remote_fetch_summary:
  * @self: Self
index 08d3d408bec9ac12cde2d5e5f9e9f28869f18aaa..7694d40c658a1d417a32861ba28396cfb8dcc103 100644 (file)
@@ -1425,6 +1425,46 @@ gboolean      ostree_repo_remote_get_gpg_verify_summary (OstreeRepo  *self,
                                                          const char  *name,
                                                          gboolean    *out_gpg_verify_summary,
                                                          GError     **error);
+
+/**
+ * OSTREE_GPG_KEY_GVARIANT_FORMAT:
+ *
+ * - a(a{sv}) - Array of subkeys. Each a{sv} dictionary represents a
+ *   subkey. The primary key is the first subkey. The following keys are
+ *   currently recognized:
+ *   - key: `fingerprint`, value: `s`, key fingerprint hexadecimal string
+ *   - key: `created`, value: `x`, key creation timestamp (seconds since
+ *     the Unix epoch in UTC, big-endian)
+ *   - key: `expires`, value: `x`, key expiration timestamp (seconds since
+ *     the Unix epoch in UTC, big-endian). If this value is 0, the key does
+ *     not expire.
+ *   - key: `revoked`, value: `b`, whether key is revoked
+ *   - key: `expired`, value: `b`, whether key is expired
+ *   - key: `invalid`, value: `b`, whether key is invalid
+ * - a(a{sv}) - Array of user IDs. Each a{sv} dictionary represents a
+ *   user ID. The following keys are currently recognized:
+ *   - key: `uid`, value: `s`, full user ID (name, email and comment)
+ *   - key: `name`, value: `s`, user ID name component
+ *   - key: `comment`, value: `s`, user ID comment component
+ *   - key: `email`, value: `s`, user ID email component
+ *   - key: `revoked`, value: `b`, whether user ID is revoked
+ *   - key: `invalid`, value: `b`, whether user ID is invalid
+ * - a{sv} - Additional metadata dictionary. There are currently no
+ *   additional metadata keys defined.
+ *
+ * Since: 2021.4
+ */
+#define OSTREE_GPG_KEY_GVARIANT_STRING "(a(a{sv})a(a{sv})a{sv})"
+#define OSTREE_GPG_KEY_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_GPG_KEY_GVARIANT_STRING)
+
+_OSTREE_PUBLIC
+gboolean      ostree_repo_remote_get_gpg_keys (OstreeRepo          *self,
+                                               const char          *name,
+                                               const char * const  *key_ids,
+                                               GPtrArray          **out_keys,
+                                               GCancellable        *cancellable,
+                                               GError             **error);
+
 _OSTREE_PUBLIC
 gboolean ostree_repo_remote_gpg_import (OstreeRepo         *self,
                                         const char         *name,