From dba2cdcbac5c064574f44a56714324e71ed9500b Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Fri, 26 Jul 2019 09:38:23 -0600 Subject: [PATCH 01/12] lib/repo: Factor out GPG verifier key imports Currently the verifier only imports all the GPG keys when verifying data, but it would also be useful for inspecting the trusted keys. --- src/libostree/ostree-gpg-verifier.c | 109 ++++++++++++++++------------ 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/src/libostree/ostree-gpg-verifier.c b/src/libostree/ostree-gpg-verifier.c index 95ed36eed6..88850d467e 100644 --- a/src/libostree/ostree-gpg-verifier.c +++ b/src/libostree/ostree-gpg-verifier.c @@ -91,43 +91,16 @@ verify_result_finalized_cb (gpointer data, (void) glnx_shutil_rm_rf_at (AT_FDCWD, tmp_dir, NULL, NULL); } -OstreeGpgVerifyResult * -_ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, - GBytes *signed_data, - GBytes *signatures, - GCancellable *cancellable, - GError **error) +static gboolean +_ostree_gpg_verifier_import_keys (OstreeGpgVerifier *self, + gpgme_ctx_t gpgme_ctx, + GOutputStream *pubring_stream, + GCancellable *cancellable, + GError **error) { GLNX_AUTO_PREFIX_ERROR("GPG", error); - gpgme_error_t gpg_error = 0; - g_auto(gpgme_data_t) data_buffer = NULL; - g_auto(gpgme_data_t) signature_buffer = NULL; - g_autofree char *tmp_dir = NULL; - g_autoptr(GOutputStream) target_stream = NULL; - OstreeGpgVerifyResult *result = NULL; - gboolean success = FALSE; - GList *link; - int armor; - - /* GPGME has no API for using multiple keyrings (aka, gpg --keyring), - * so we concatenate all the keyring files into one pubring.gpg in a - * temporary directory, then tell GPGME to use that directory as the - * home directory. */ - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - goto out; - - result = g_initable_new (OSTREE_TYPE_GPG_VERIFY_RESULT, - cancellable, error, NULL); - if (result == NULL) - goto out; - if (!ot_gpgme_ctx_tmp_home_dir (result->context, - &tmp_dir, &target_stream, - cancellable, error)) - goto out; - - for (link = self->keyrings; link != NULL; link = link->next) + for (GList *link = self->keyrings; link != NULL; link = link->next) { g_autoptr(GFileInputStream) source_stream = NULL; GFile *keyring_file = link->data; @@ -145,15 +118,15 @@ _ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, else if (local_error != NULL) { g_propagate_error (error, local_error); - goto out; + return FALSE; } - bytes_written = g_output_stream_splice (target_stream, + bytes_written = g_output_stream_splice (pubring_stream, G_INPUT_STREAM (source_stream), G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, cancellable, error); if (bytes_written < 0) - goto out; + return FALSE; } for (guint i = 0; i < self->keyring_data->len; i++) @@ -162,23 +135,25 @@ _ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, gsize len; gsize bytes_written; const guint8 *buf = g_bytes_get_data (keyringd, &len); - if (!g_output_stream_write_all (target_stream, buf, len, &bytes_written, + if (!g_output_stream_write_all (pubring_stream, buf, len, &bytes_written, cancellable, error)) - goto out; + return FALSE; } - if (!g_output_stream_close (target_stream, cancellable, error)) - goto out; + if (!g_output_stream_close (pubring_stream, cancellable, error)) + return FALSE; /* Save the previous armor value - we need it on for importing ASCII keys */ - armor = gpgme_get_armor (result->context); - gpgme_set_armor (result->context, 1); + gboolean ret = FALSE; + int armor = gpgme_get_armor (gpgme_ctx); + gpgme_set_armor (gpgme_ctx, 1); /* Now, use the API to import ASCII-armored keys */ if (self->key_ascii_files) { for (guint i = 0; i < self->key_ascii_files->len; i++) { + gpgme_error_t gpg_error; const char *path = self->key_ascii_files->pdata[i]; glnx_autofd int fd = -1; g_auto(gpgme_data_t) kdata = NULL; @@ -193,7 +168,7 @@ _ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, goto out; } - gpg_error = gpgme_op_import (result->context, kdata); + gpg_error = gpgme_op_import (gpgme_ctx, kdata); if (gpg_error != GPG_ERR_NO_ERROR) { ot_gpgme_throw (gpg_error, error, "Failed to import key"); @@ -202,7 +177,51 @@ _ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, } } - gpgme_set_armor (result->context, armor); + ret = TRUE; + + out: + gpgme_set_armor (gpgme_ctx, armor); + + return ret; +} + +OstreeGpgVerifyResult * +_ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self, + GBytes *signed_data, + GBytes *signatures, + GCancellable *cancellable, + GError **error) +{ + GLNX_AUTO_PREFIX_ERROR("GPG", error); + gpgme_error_t gpg_error = 0; + g_auto(gpgme_data_t) data_buffer = NULL; + g_auto(gpgme_data_t) signature_buffer = NULL; + g_autofree char *tmp_dir = NULL; + g_autoptr(GOutputStream) target_stream = NULL; + OstreeGpgVerifyResult *result = NULL; + gboolean success = FALSE; + + /* GPGME has no API for using multiple keyrings (aka, gpg --keyring), + * so we concatenate all the keyring files into one pubring.gpg in a + * temporary directory, then tell GPGME to use that directory as the + * home directory. */ + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; + + result = g_initable_new (OSTREE_TYPE_GPG_VERIFY_RESULT, + cancellable, error, NULL); + if (result == NULL) + goto out; + + if (!ot_gpgme_ctx_tmp_home_dir (result->context, + &tmp_dir, &target_stream, + cancellable, error)) + goto out; + + if (!_ostree_gpg_verifier_import_keys (self, result->context, target_stream, + cancellable, error)) + goto out; /* Both the signed data and signature GBytes instances will outlive the * gpgme_data_t structs, so we can safely reuse the GBytes memory buffer From c8715c123e7979b078d45fd18e7cf5a276eda698 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 10:36:11 -0600 Subject: [PATCH 02/12] lib/repo: Factor out GPG verifier preparation In order to use the GPG verifier, it needs to be seeded with GPG keys after instantation. Currently this is only used for verifying data, but it will also be used for getting a list of trusted GPG keys in a subsequent commit. --- src/libostree/ostree-repo.c | 63 +++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index b90e1c1374..d7b383742c 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -5338,28 +5338,25 @@ find_keyring (OstreeRepo *self, return TRUE; } -static OstreeGpgVerifyResult * -_ostree_repo_gpg_verify_data_internal (OstreeRepo *self, - const gchar *remote_name, - GBytes *data, - GBytes *signatures, - GFile *keyringdir, - GFile *extra_keyring, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(OstreeGpgVerifier) verifier = NULL; +static gboolean +_ostree_repo_gpg_prepare_verifier (OstreeRepo *self, + const gchar *remote_name, + GFile *keyringdir, + GFile *extra_keyring, + OstreeGpgVerifier **out_verifier, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(OstreeGpgVerifier) verifier = _ostree_gpg_verifier_new (); gboolean add_global_keyring_dir = TRUE; - verifier = _ostree_gpg_verifier_new (); - if (remote_name == OSTREE_ALL_REMOTES) { /* Add all available remote keyring files. */ if (!_ostree_gpg_verifier_add_keyring_dir_at (verifier, self->repo_dir_fd, ".", cancellable, error)) - return NULL; + return FALSE; } else if (remote_name != NULL) { @@ -5369,11 +5366,11 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, remote = _ostree_repo_get_remote_inherited (self, remote_name, error); if (remote == NULL) - return NULL; + return FALSE; g_autoptr(GBytes) keyring_data = NULL; if (!find_keyring (self, remote, &keyring_data, cancellable, error)) - return NULL; + return FALSE; if (keyring_data != NULL) { @@ -5389,14 +5386,14 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, ";,", &gpgkeypath_list, error)) - return NULL; + return FALSE; if (gpgkeypath_list) { for (char **iter = gpgkeypath_list; *iter != NULL; ++iter) if (!_ostree_gpg_verifier_add_keyfile_path (verifier, *iter, cancellable, error)) - return NULL; + return FALSE; } } @@ -5404,20 +5401,46 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, { /* Use the deprecated global keyring directory. */ if (!_ostree_gpg_verifier_add_global_keyring_dir (verifier, cancellable, error)) - return NULL; + return FALSE; } if (keyringdir) { if (!_ostree_gpg_verifier_add_keyring_dir (verifier, keyringdir, cancellable, error)) - return NULL; + return FALSE; } if (extra_keyring != NULL) { _ostree_gpg_verifier_add_keyring_file (verifier, extra_keyring); } + if (out_verifier != NULL) + *out_verifier = g_steal_pointer (&verifier); + + return TRUE; +} + +static OstreeGpgVerifyResult * +_ostree_repo_gpg_verify_data_internal (OstreeRepo *self, + const gchar *remote_name, + GBytes *data, + GBytes *signatures, + GFile *keyringdir, + GFile *extra_keyring, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(OstreeGpgVerifier) verifier = NULL; + if (!_ostree_repo_gpg_prepare_verifier (self, + remote_name, + keyringdir, + extra_keyring, + &verifier, + cancellable, + error)) + return NULL; + return _ostree_gpg_verifier_check_signature (verifier, data, signatures, From fc073654dca64dacdc0067bd35ecb82f8c794fe3 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Wed, 14 Jul 2021 11:04:59 -0600 Subject: [PATCH 03/12] lib/repo: Allow preparing GPG verifier without global keyrings Currently the verifier decides whether to include the global keyrings based on whether the specified remote has its own keyring or not. Allow callers to exclude the global keyrings even when that's not the case. This will be used in a subsequent commit in order to get the GPG keys only associated with a remote. --- src/libostree/ostree-repo.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index d7b383742c..254f7010ef 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -5343,12 +5343,12 @@ _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) { g_autoptr(OstreeGpgVerifier) verifier = _ostree_gpg_verifier_new (); - gboolean add_global_keyring_dir = TRUE; if (remote_name == OSTREE_ALL_REMOTES) { @@ -5375,7 +5375,7 @@ _ostree_repo_gpg_prepare_verifier (OstreeRepo *self, if (keyring_data != NULL) { _ostree_gpg_verifier_add_keyring_data (verifier, keyring_data, remote->keyring); - add_global_keyring_dir = FALSE; + add_global_keyrings = FALSE; } g_auto(GStrv) gpgkeypath_list = NULL; @@ -5397,7 +5397,7 @@ _ostree_repo_gpg_prepare_verifier (OstreeRepo *self, } } - if (add_global_keyring_dir) + if (add_global_keyrings) { /* Use the deprecated global keyring directory. */ if (!_ostree_gpg_verifier_add_global_keyring_dir (verifier, cancellable, error)) @@ -5436,6 +5436,7 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, remote_name, keyringdir, extra_keyring, + TRUE, &verifier, cancellable, error)) From a50f6d0b9fa1d3079ff5bf78e46da3a635a37611 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 13:36:00 -0600 Subject: [PATCH 04/12] lib/repo: Add ostree_repo_remote_get_gpg_keys() 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 | 6 +- apidoc/ostree-sections.txt | 3 + src/libostree/libostree-devel.sym | 5 ++ src/libostree/ostree-gpg-verifier.c | 85 +++++++++++++++++++ src/libostree/ostree-gpg-verifier.h | 6 ++ src/libostree/ostree-repo.c | 124 ++++++++++++++++++++++++++++ src/libostree/ostree-repo.h | 40 +++++++++ 7 files changed, 266 insertions(+), 3 deletions(-) diff --git a/Makefile-libostree.am b/Makefile-libostree.am index dd39697406..d40de48d13 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -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= diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 2da1d74969..4d02755558 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -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 diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index e3cd14a4af..75bc464770 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -22,6 +22,11 @@ - 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 diff --git a/src/libostree/ostree-gpg-verifier.c b/src/libostree/ostree-gpg-verifier.c index 88850d467e..e9f5c5e332 100644 --- a/src/libostree/ostree-gpg-verifier.c +++ b/src/libostree/ostree-gpg-verifier.c @@ -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, diff --git a/src/libostree/ostree-gpg-verifier.h b/src/libostree/ostree-gpg-verifier.h index 634d33b299..3d803c4953 100644 --- a/src/libostree/ostree-gpg-verifier.h +++ b/src/libostree/ostree-gpg-verifier.h @@ -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, diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 254f7010ef..b20aa61793 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2353,6 +2353,130 @@ ostree_repo_remote_gpg_import (OstreeRepo *self, #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 diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 08d3d408be..7694d40c65 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -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, From 74fb0c5f7826e90efcc905ba75cf8a176d61f0a5 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 10:10:50 -0600 Subject: [PATCH 05/12] bin/remote: Add list-gpg-keys subcommand This provides a wrapper for the `ostree_repo_remote_get_gpg_keys` function to show the GPG keys associated with a remote. This is particularly useful for validating that GPG key updates have been applied. Tests are added, which checks the `ostree_repo_remote_get_gpg_keys` API by extension. --- Makefile-ostree.am | 1 + Makefile-tests.am | 1 + bash/ostree | 35 +++++ man/ostree-remote.xml | 9 +- src/ostree/ot-builtin-remote.c | 3 + src/ostree/ot-dump.c | 113 ++++++++++++++- src/ostree/ot-dump.h | 3 + src/ostree/ot-remote-builtin-list-gpg-keys.c | 66 +++++++++ src/ostree/ot-remote-builtins.h | 1 + tests/test-remote-list-gpg-keys.sh | 144 +++++++++++++++++++ 10 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 src/ostree/ot-remote-builtin-list-gpg-keys.c create mode 100755 tests/test-remote-list-gpg-keys.sh diff --git a/Makefile-ostree.am b/Makefile-ostree.am index fd5ec9dea6..a5509f7ca0 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -105,6 +105,7 @@ ostree_SOURCES += \ if USE_GPGME ostree_SOURCES += \ src/ostree/ot-remote-builtin-gpg-import.c \ + src/ostree/ot-remote-builtin-list-gpg-keys.c \ $(NULL) endif diff --git a/Makefile-tests.am b/Makefile-tests.am index 295c734ec0..1997bfd842 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -152,6 +152,7 @@ _installed_or_uninstalled_test_scripts = \ if USE_GPGME _installed_or_uninstalled_test_scripts += \ tests/test-remote-gpg-import.sh \ + tests/test-remote-list-gpg-keys.sh \ tests/test-gpg-signed-commit.sh \ tests/test-admin-gpg.sh \ $(NULL) diff --git a/bash/ostree b/bash/ostree index d1de853068..32d5e3174d 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1235,6 +1235,40 @@ _ostree_remote_list_cookies() { return 0 } +_ostree_remote_list_gpg_keys() { + local boolean_options=" + $main_boolean_options + " + + local options_with_args=" + --repo + " + + local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) + + case "$prev" in + --repo) + __ostree_compreply_dirs_only + return 0 + ;; + esac + + case "$cur" in + -*) + local all_options="$boolean_options $options_with_args" + __ostree_compreply_all_options + ;; + *) + local argpos=$( __ostree_pos_first_nonflag $( __ostree_to_alternatives "$options_with_args" ) ) + + if [ $cword -eq $argpos ]; then + __ostree_compreply_remotes + fi + esac + + return 0 +} + _ostree_remote_refs() { local boolean_options=" $main_boolean_options @@ -1349,6 +1383,7 @@ _ostree_remote() { gpg-import list list-cookies + list-gpg-keys refs show-url summary diff --git a/man/ostree-remote.xml b/man/ostree-remote.xml index 407f7e3d2c..928bf9b5f8 100644 --- a/man/ostree-remote.xml +++ b/man/ostree-remote.xml @@ -65,6 +65,9 @@ Boston, MA 02111-1307, USA. ostree remote gpg-import OPTIONS NAME KEY-ID + + ostree remote list-gpg-keys NAME + ostree remote refs NAME @@ -106,7 +109,11 @@ Boston, MA 02111-1307, USA. for more information. - The gpg-import subcommand can associate GPG keys to a specific remote repository for use when pulling signed commits from that repository (if GPG verification is enabled). + The gpg-import subcommand can associate GPG + keys to a specific remote repository for use when pulling signed + commits from that repository (if GPG verification is enabled). The + list-gpg-keys subcommand can be used to see the + GPG keys currently associated with a remote repository. The GPG keys to import may be in binary OpenPGP format or ASCII armored. The optional KEY-ID list can restrict which keys are imported from a keyring file or input stream. All keys are imported if this list is omitted. If neither nor options are given, then keys are imported from the user's personal GPG keyring. diff --git a/src/ostree/ot-builtin-remote.c b/src/ostree/ot-builtin-remote.c index 6b3f6a268d..7028eacc75 100644 --- a/src/ostree/ot-builtin-remote.c +++ b/src/ostree/ot-builtin-remote.c @@ -44,6 +44,9 @@ static OstreeCommand remote_subcommands[] = { { "gpg-import", OSTREE_BUILTIN_FLAG_NONE, ot_remote_builtin_gpg_import, "Import GPG keys" }, + { "list-gpg-keys", OSTREE_BUILTIN_FLAG_NONE, + ot_remote_builtin_list_gpg_keys, + "Show remote GPG keys" }, #endif /* OSTREE_DISABLE_GPGME */ #ifdef HAVE_LIBCURL_OR_LIBSOUP { "add-cookie", OSTREE_BUILTIN_FLAG_NONE, diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index a8ed54a2ee..1c0f04a9dd 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -53,6 +53,7 @@ ot_dump_variant (GVariant *variant) static gchar * format_timestamp (guint64 timestamp, + gboolean local_tz, GError **error) { GDateTime *dt; @@ -66,7 +67,19 @@ format_timestamp (guint64 timestamp, return NULL; } - str = g_date_time_format (dt, "%Y-%m-%d %H:%M:%S +0000"); + if (local_tz) + { + /* Convert to local time and display in the locale's preferred + * representation. + */ + g_autoptr(GDateTime) dt_local = g_date_time_to_local (dt); + str = g_date_time_format (dt_local, "%c"); + } + else + { + str = g_date_time_format (dt, "%Y-%m-%d %H:%M:%S +0000"); + } + g_date_time_unref (dt); return str; @@ -124,7 +137,7 @@ dump_commit (GVariant *variant, &subject, &body, ×tamp, NULL, NULL); timestamp = GUINT64_FROM_BE (timestamp); - str = format_timestamp (timestamp, &local_error); + str = format_timestamp (timestamp, FALSE, &local_error); if (!str) { g_assert (local_error); /* Pacify static analysis */ @@ -390,3 +403,99 @@ ot_dump_summary_bytes (GBytes *summary_bytes, g_print ("%s: %s\n", key, value_str); } } + +static gboolean +dump_gpg_subkey (GVariant *subkey, + gboolean primary, + GError **error) +{ + const gchar *fingerprint = NULL; + gint64 created = 0; + gint64 expires = 0; + gboolean revoked = FALSE; + gboolean expired = FALSE; + gboolean invalid = FALSE; + (void) g_variant_lookup (subkey, "fingerprint", "&s", &fingerprint); + (void) g_variant_lookup (subkey, "created", "x", &created); + (void) g_variant_lookup (subkey, "expires", "x", &expires); + (void) g_variant_lookup (subkey, "revoked", "b", &revoked); + (void) g_variant_lookup (subkey, "expired", "b", &expired); + (void) g_variant_lookup (subkey, "invalid", "b", &invalid); + + /* Convert timestamps from big endian if needed */ + created = GINT64_FROM_BE (created); + expires = GINT64_FROM_BE (expires); + + g_print ("%s: %s%s%s\n", + primary ? "Key" : " Subkey", + fingerprint, + revoked ? " (revoked)" : "", + invalid ? " (invalid)" : ""); + + g_autofree gchar *created_str = format_timestamp (created, TRUE, + error); + if (created_str == NULL) + return FALSE; + g_print ("%sCreated: %s\n", + primary ? " " : " ", + created_str); + + if (expires > 0) + { + g_autofree gchar *expires_str = format_timestamp (expires, TRUE, + error); + if (expires_str == NULL) + return FALSE; + g_print ("%s%s: %s\n", + primary ? " " : " ", + expired ? "Expired" : "Expires", + expires_str); + } + + return TRUE; +} + +gboolean +ot_dump_gpg_key (GVariant *key, + GError **error) +{ + if (!g_variant_is_of_type (key, OSTREE_GPG_KEY_GVARIANT_FORMAT)) + return glnx_throw (error, "GPG key variant type doesn't match '%s'", + OSTREE_GPG_KEY_GVARIANT_STRING); + + g_autoptr(GVariant) subkeys_v = g_variant_get_child_value (key, 0); + GVariantIter subkeys_iter; + g_variant_iter_init (&subkeys_iter, subkeys_v); + + g_autoptr(GVariant) primary_key = NULL; + g_variant_iter_next (&subkeys_iter, "(@a{sv})", &primary_key); + if (!dump_gpg_subkey (primary_key, TRUE, error)) + return FALSE; + + g_autoptr(GVariant) uids_v = g_variant_get_child_value (key, 1); + GVariantIter uids_iter; + g_variant_iter_init (&uids_iter, uids_v); + GVariant *uid_v = NULL; + while (g_variant_iter_loop (&uids_iter, "(@a{sv})", &uid_v)) + { + const gchar *uid = NULL; + gboolean revoked = FALSE; + gboolean invalid = FALSE; + (void) g_variant_lookup (uid_v, "uid", "&s", &uid); + (void) g_variant_lookup (uid_v, "revoked", "b", &revoked); + (void) g_variant_lookup (uid_v, "invalid", "b", &invalid); + g_print (" UID: %s%s%s\n", + uid, + revoked ? " (revoked)" : "", + invalid ? " (invalid)" : ""); + } + + GVariant *subkey = NULL; + while (g_variant_iter_loop (&subkeys_iter, "(@a{sv})", &subkey)) + { + if (!dump_gpg_subkey (subkey, FALSE, error)) + return FALSE; + } + + return TRUE; +} diff --git a/src/ostree/ot-dump.h b/src/ostree/ot-dump.h index 0e1952af81..02e2f1a65c 100644 --- a/src/ostree/ot-dump.h +++ b/src/ostree/ot-dump.h @@ -42,3 +42,6 @@ void ot_dump_object (OstreeObjectType objtype, void ot_dump_summary_bytes (GBytes *summary_bytes, OstreeDumpFlags flags); + +gboolean ot_dump_gpg_key (GVariant *key, + GError **error); diff --git a/src/ostree/ot-remote-builtin-list-gpg-keys.c b/src/ostree/ot-remote-builtin-list-gpg-keys.c new file mode 100644 index 0000000000..84d0f1a309 --- /dev/null +++ b/src/ostree/ot-remote-builtin-list-gpg-keys.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * SPDX-License-Identifier: LGPL-2.0+ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "otutil.h" + +#include "ot-main.h" +#include "ot-dump.h" +#include "ot-remote-builtins.h" + +/* ATTENTION: + * Please remember to update the bash-completion script (bash/ostree) and + * man page (man/ostree-remote.xml) when changing the option list. + */ + +static GOptionEntry option_entries[] = { + { NULL } +}; + +gboolean +ot_remote_builtin_list_gpg_keys (int argc, + char **argv, + OstreeCommandInvocation *invocation, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("NAME"); + g_autoptr(OstreeRepo) repo = NULL; + if (!ostree_option_context_parse (context, option_entries, &argc, &argv, + invocation, &repo, cancellable, error)) + return FALSE; + + const char *remote_name = (argc > 1) ? argv[1] : NULL; + + g_autoptr(GPtrArray) keys = NULL; + if (!ostree_repo_remote_get_gpg_keys (repo, remote_name, NULL, &keys, + cancellable, error)) + return FALSE; + + for (guint i = 0; i < keys->len; i++) + { + if (!ot_dump_gpg_key (keys->pdata[i], error)) + return FALSE; + } + + return TRUE; +} diff --git a/src/ostree/ot-remote-builtins.h b/src/ostree/ot-remote-builtins.h index 71b2365a3b..4b46af199f 100644 --- a/src/ostree/ot-remote-builtins.h +++ b/src/ostree/ot-remote-builtins.h @@ -32,6 +32,7 @@ G_BEGIN_DECLS BUILTINPROTO(add); BUILTINPROTO(delete); BUILTINPROTO(gpg_import); +BUILTINPROTO(list_gpg_keys); BUILTINPROTO(list); #ifdef HAVE_LIBCURL_OR_LIBSOUP BUILTINPROTO(add_cookie); diff --git a/tests/test-remote-list-gpg-keys.sh b/tests/test-remote-list-gpg-keys.sh new file mode 100755 index 0000000000..5ad6c9f2e1 --- /dev/null +++ b/tests/test-remote-list-gpg-keys.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# +# Copyright © 2021 Endless OS Foundation LLC +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +# We don't want OSTREE_GPG_HOME used for most of these tests. +emptydir=${test_tmpdir}/empty +trusteddir=${OSTREE_GPG_HOME} +mkdir ${emptydir} +OSTREE_GPG_HOME=${emptydir} + +# Key listings show dates using the local timezone, so specify UTC for +# consistency. +export TZ=UTC + +# Some tests require an appropriate gpg +num_non_gpg_tests=5 +num_gpg_tests=2 +num_tests=$((num_non_gpg_tests + num_gpg_tests)) + +echo "1..${num_tests}" + +setup_test_repository "archive" + +cd ${test_tmpdir} +${OSTREE} remote add R1 http://example.com/repo + +# No remote keyring should list no keys. +${OSTREE} remote list-gpg-keys R1 > result +assert_file_empty result + +echo "ok remote no keyring" + +# Make the global keyring available and make sure there are still no +# keys found for a specified remote. +OSTREE_GPG_HOME=${trusteddir} +${OSTREE} remote list-gpg-keys R1 > result +OSTREE_GPG_HOME=${emptydir} +assert_file_empty result + +echo "ok remote with global keyring" + +# Import a key and check that it's listed +${OSTREE} remote gpg-import --keyring ${TEST_GPG_KEYHOME}/key1.asc R1 +${OSTREE} remote list-gpg-keys R1 > result +cat > expected <<"EOF" +Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA + Created: Tue Sep 10 02:29:42 2013 + UID: Ostree Tester + Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 + Created: Tue Sep 10 02:29:42 2013 +EOF +assert_files_equal result expected + +echo "ok remote with keyring" + +# Check the global keys with no keyring +OSTREE_GPG_HOME=${emptydir} +${OSTREE} remote list-gpg-keys > result +assert_file_empty result + +echo "ok global no keyring" + +# Now check the global keys with a keyring +OSTREE_GPG_HOME=${trusteddir} +${OSTREE} remote list-gpg-keys > result +OSTREE_GPG_HOME=${emptydir} +cat > expected <<"EOF" +Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA + Created: Tue Sep 10 02:29:42 2013 + UID: Ostree Tester + Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 + Created: Tue Sep 10 02:29:42 2013 +Key: 7B3B1020D74479687FDB2273D8228CFECA950D41 + Created: Tue Mar 17 14:00:32 2015 + UID: Ostree Tester II + Subkey: 1EFA95C06EB1EB91754575E004B69C2560D53993 + Created: Tue Mar 17 14:00:32 2015 +Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67 + Created: Tue Mar 17 14:01:05 2015 + UID: Ostree Tester III + Subkey: 0E45E48CBF7B360C0E04443E0C601A7402416340 + Created: Tue Mar 17 14:01:05 2015 +EOF +assert_files_equal result expected + +echo "ok global with keyring" + +# Tests checking for expiration and revocation listings require gpg. +GPG=$(which_gpg) +if [ -z "${GPG}" ]; then + # Print a skip message per skipped test + for (( i = 0; i < num_gpg_tests; i++ )); do + echo "ok # SKIP this test requires gpg" + done +else + # The GPG private keyring in gpghome is in the older secring.gpg + # format, but we're likely using a newer gpg. Normally it's + # implicitly migrated to the newer format, but this test hasn't + # signed anything, so the private keys haven't been loaded. Force + # the migration by listing the private keys. + ${GPG} --homedir=${test_tmpdir}/gpghome -K >/dev/null + + # Expire key1, wait for it to be expired and re-import it. + ${GPG} --homedir=${test_tmpdir}/gpghome --quick-set-expire ${TEST_GPG_KEYFPR_1} seconds=1 + sleep 2 + ${GPG} --homedir=${test_tmpdir}/gpghome --armor --export ${TEST_GPG_KEYID_1} > ${test_tmpdir}/key1expired.asc + ${OSTREE} remote gpg-import --keyring ${test_tmpdir}/key1expired.asc R1 + ${OSTREE} remote list-gpg-keys R1 > result + assert_file_has_content result "^ Expired:" + + echo "ok remote expired key" + + # Revoke key1 and re-import it. + ${GPG} --homedir=${TEST_GPG_KEYHOME} --import ${TEST_GPG_KEYHOME}/revocations/key1.rev + ${GPG} --homedir=${test_tmpdir}/gpghome --armor --export ${TEST_GPG_KEYID_1} > ${test_tmpdir}/key1revoked.asc + ${OSTREE} remote gpg-import --keyring ${test_tmpdir}/key1revoked.asc R1 + ${OSTREE} remote list-gpg-keys R1 > result + assert_file_has_content result "^Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA (revoked)" + assert_file_has_content result "^ UID: Ostree Tester (revoked)" + assert_file_has_content result "^ Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 (revoked)" + + echo "ok remote revoked key" +fi From fbff05e28d0e0d3c8424a7d4a5652a253a553b04 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 10:09:57 -0600 Subject: [PATCH 06/12] libotutil: Import implementation of zbase32 encoding This will be used to implement the PGP Web Key Directory (WKD) URL generation. This is a slightly cleaned up implementation[1] taken from the zbase32 author's original implementation[2]. It provides a single zbase32_encode API to convert a set of bytes to the zbase32 encoding. I believe this should be acceptable for inclusion in ostree. The license in the source files is BSD style while the original repo LICENSE file claims the Creative Commons CC0 1.0 Universal license, which is public domain. 1. https://github.com/dbnicholson/libbase32/tree/for-ostree 2. https://github.com/zooko/libbase32 --- Makefile-otutil.am | 2 + src/libotutil/zbase32.c | 141 ++++++++++++++++++++++++++++++++++++++++ src/libotutil/zbase32.h | 49 ++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 src/libotutil/zbase32.c create mode 100644 src/libotutil/zbase32.h diff --git a/Makefile-otutil.am b/Makefile-otutil.am index e8901b57da..7bc87b6a4f 100644 --- a/Makefile-otutil.am +++ b/Makefile-otutil.am @@ -49,6 +49,8 @@ if USE_GPGME libotutil_la_SOURCES += \ src/libotutil/ot-gpg-utils.c \ src/libotutil/ot-gpg-utils.h \ + src/libotutil/zbase32.c \ + src/libotutil/zbase32.h \ $(NULL) endif diff --git a/src/libotutil/zbase32.c b/src/libotutil/zbase32.c new file mode 100644 index 0000000000..39fa97a465 --- /dev/null +++ b/src/libotutil/zbase32.c @@ -0,0 +1,141 @@ +/** + * copyright 2002, 2003 Bryce "Zooko" Wilcox-O'Hearn + * mailto:zooko@zooko.com + * + * See the end of this file for the free software, open source license (BSD-style). + */ +#include "zbase32.h" + +#include +#include +#include +#include /* XXX only for debug printfs */ + +static const char*const chars="ybndrfg8ejkmcpqxot1uwisza345h769"; + +/* Types from zstr */ +/** + * A zstr is simply an unsigned int length and a pointer to a buffer of + * unsigned chars. + */ +typedef struct { + size_t len; /* the length of the string (not counting the null-terminating character) */ + unsigned char* buf; /* pointer to the first byte */ +} zstr; + +/** + * A zstr is simply an unsigned int length and a pointer to a buffer of + * const unsigned chars. + */ +typedef struct { + size_t len; /* the length of the string (not counting the null-terminating character) */ + const unsigned char* buf; /* pointer to the first byte */ +} czstr; + +/* Functions from zstr */ +static zstr +new_z(const size_t len) +{ + zstr result; + result.buf = (unsigned char *)malloc(len+1); + if (result.buf == NULL) { + result.len = 0; + return result; + } + result.len = len; + result.buf[len] = '\0'; + return result; +} + +/* Functions from zutil */ +static size_t +divceil(size_t n, size_t d) +{ + return n/d+((n%d)!=0); +} + +static zstr b2a_l_extra_Duffy(const czstr os, const size_t lengthinbits) +{ + zstr result = new_z(divceil(os.len*8, 5)); /* if lengthinbits is not a multiple of 8 then this is allocating space for 0, 1, or 2 extra quintets that will be truncated at the end of this function if they are not needed */ + if (result.buf == NULL) + return result; + + unsigned char* resp = result.buf + result.len; /* pointer into the result buffer, initially pointing to the "one-past-the-end" quintet */ + const unsigned char* osp = os.buf + os.len; /* pointer into the os buffer, initially pointing to the "one-past-the-end" octet */ + + /* Now this is a real live Duff's device. You gotta love it. */ + unsigned long x=0; /* to hold up to 32 bits worth of the input */ + switch ((osp - os.buf) % 5) { + case 0: + do { + x = *--osp; + *--resp = chars[x % 32]; /* The least sig 5 bits go into the final quintet. */ + x /= 32; /* ... now we have 3 bits worth in x... */ + case 4: + x |= ((unsigned long)(*--osp)) << 3; /* ... now we have 11 bits worth in x... */ + *--resp = chars[x % 32]; + x /= 32; /* ... now we have 6 bits worth in x... */ + *--resp = chars[x % 32]; + x /= 32; /* ... now we have 1 bits worth in x... */ + case 3: + x |= ((unsigned long)(*--osp)) << 1; /* The 8 bits from the 2-indexed octet. So now we have 9 bits worth in x... */ + *--resp = chars[x % 32]; + x /= 32; /* ... now we have 4 bits worth in x... */ + case 2: + x |= ((unsigned long)(*--osp)) << 4; /* The 8 bits from the 1-indexed octet. So now we have 12 bits worth in x... */ + *--resp = chars[x%32]; + x /= 32; /* ... now we have 7 bits worth in x... */ + *--resp = chars[x%32]; + x /= 32; /* ... now we have 2 bits worth in x... */ + case 1: + x |= ((unsigned long)(*--osp)) << 2; /* The 8 bits from the 0-indexed octet. So now we have 10 bits worth in x... */ + *--resp = chars[x%32]; + x /= 32; /* ... now we have 5 bits worth in x... */ + *--resp = chars[x]; + } while (osp > os.buf); + } /* switch ((osp - os.buf) % 5) */ + + /* truncate any unused trailing zero quintets */ + result.len = divceil(lengthinbits, 5); + result.buf[result.len] = '\0'; + return result; +} + +static zstr b2a_l(const czstr os, const size_t lengthinbits) +{ + return b2a_l_extra_Duffy(os, lengthinbits); +} + +static zstr b2a(const czstr os) +{ + return b2a_l(os, os.len*8); +} + +char * +zbase32_encode(const unsigned char *data, size_t length) +{ + czstr input = { length, data }; + zstr output = b2a(input); + return (char *)output.buf; +} + +/** + * Copyright (c) 2002 Bryce "Zooko" Wilcox-O'Hearn + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software to deal in this software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of this software, and to permit + * persons to whom this software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of this software. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THIS SOFTWARE. + */ diff --git a/src/libotutil/zbase32.h b/src/libotutil/zbase32.h new file mode 100644 index 0000000000..bf9cf6832d --- /dev/null +++ b/src/libotutil/zbase32.h @@ -0,0 +1,49 @@ +/** + * copyright 2002, 2003 Bryce "Zooko" Wilcox-O'Hearn + * mailto:zooko@zooko.com + * + * See the end of this file for the free software, open source license (BSD-style). + */ +#ifndef __INCL_base32_h +#define __INCL_base32_h + +static char const* const base32_h_cvsid = "$Id: base32.h,v 1.11 2003/12/15 01:16:19 zooko Exp $"; + +static int const base32_vermaj = 0; +static int const base32_vermin = 9; +static int const base32_vermicro = 12; +static char const* const base32_vernum = "0.9.12"; + +#include +#include + +/** + * @param data to be zbase-32 encoded + * @param length size of the data buffer + * + * @return an allocated string containing the zbase-32 encoded representation + */ +char *zbase32_encode(const unsigned char *data, size_t length); + +#endif /* #ifndef __INCL_base32_h */ + +/** + * Copyright (c) 2002 Bryce "Zooko" Wilcox-O'Hearn + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software to deal in this software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of this software, and to permit + * persons to whom this software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of this software. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THIS SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THIS SOFTWARE. + */ From 4fa403aee500df058a7d3ee2b6b1db4300ad7a92 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 27 Aug 2019 10:28:10 -0600 Subject: [PATCH 07/12] libotutil: Add helper for GPG WKD update URLs Calculate the advanced and direct update URLs for the key discovery portion[1] of the OpenPGP Web Key Directory specification, and include the URLs in the key listing in ostree_repo_remote_get_gpg_keys(). These URLs can be used to locate updated GPG keys for the remote. 1. https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service#section-3.1 --- src/libotutil/ot-gpg-utils.c | 75 ++++++++++++++++++++++++++++++++++++ src/libotutil/ot-gpg-utils.h | 5 +++ 2 files changed, 80 insertions(+) diff --git a/src/libotutil/ot-gpg-utils.c b/src/libotutil/ot-gpg-utils.c index 743d941e37..4dbefdbd46 100644 --- a/src/libotutil/ot-gpg-utils.c +++ b/src/libotutil/ot-gpg-utils.c @@ -27,6 +27,7 @@ #include #include "libglnx.h" +#include "zbase32.h" /* Like glnx_throw_errno_prefix, but takes @gpg_error */ gboolean @@ -538,3 +539,77 @@ ot_gpgme_kill_agent (const char *homedir) return; } } + +/* Takes the SHA1 checksum of the local component of an email address and + * returns the zbase32 encoding. + */ +static char * +encode_wkd_local (const char *local) +{ + g_return_val_if_fail (local != NULL, NULL); + + guint8 digest[20] = { 0 }; + gsize len = sizeof (digest); + g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA1); + g_checksum_update (checksum, (const guchar *)local, -1); + g_checksum_get_digest (checksum, digest, &len); + + char *encoded = zbase32_encode (digest, len); + + /* If the returned string is NULL, then there must have been a memory + * allocation problem. Just exit immediately like g_malloc. + */ + if (encoded == NULL) + g_error ("%s: %s", G_STRLOC, g_strerror (errno)); + + return encoded; +} + +/* Implementation of OpenPGP Web Key Directory URLs as defined in + * https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service + */ +gboolean +ot_gpg_wkd_urls (const char *email, + char **out_advanced_url, + char **out_direct_url, + GError **error) +{ + g_return_val_if_fail (email != NULL, FALSE); + + g_auto(GStrv) email_parts = g_strsplit (email, "@", -1); + if (g_strv_length (email_parts) != 2) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Invalid email address \"%s\"", email); + return FALSE; + } + + g_autofree char *local_lowered = g_ascii_strdown (email_parts[0], -1); + g_autofree char *domain_lowered = g_ascii_strdown (email_parts[1], -1); + g_autofree char *local_encoded = encode_wkd_local (local_lowered); + g_autofree char *local_escaped = g_uri_escape_string (email_parts[0], NULL, FALSE); + + g_autofree char *advanced_url = g_strdup_printf ("https://openpgpkey.%s" + "/.well-known/openpgpkey" + "/%s/hu/%s?l=%s", + email_parts[1], + domain_lowered, + local_encoded, + local_escaped); + g_debug ("GPG UID \"%s\" advanced WKD URL: %s", email, advanced_url); + + g_autofree char *direct_url = g_strdup_printf ("https://%s" + "/.well-known/openpgpkey" + "/hu/%s?l=%s", + email_parts[1], + local_encoded, + local_escaped); + g_debug ("GPG UID \"%s\" direct WKD URL: %s", email, direct_url); + + if (out_advanced_url != NULL) + *out_advanced_url = g_steal_pointer (&advanced_url); + if (out_direct_url != NULL) + *out_direct_url = g_steal_pointer (&direct_url); + + return TRUE; +} diff --git a/src/libotutil/ot-gpg-utils.h b/src/libotutil/ot-gpg-utils.h index e8a240b597..b559b69573 100644 --- a/src/libotutil/ot-gpg-utils.h +++ b/src/libotutil/ot-gpg-utils.h @@ -48,4 +48,9 @@ gpgme_ctx_t ot_gpgme_new_ctx (const char *homedir, void ot_gpgme_kill_agent (const char *homedir); +gboolean ot_gpg_wkd_urls (const char *email, + char **out_advanced_url, + char **out_direct_url, + GError **error); + G_END_DECLS From 27dc5d7d389956befd9f2de7a956073899c264de Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 11:15:25 -0600 Subject: [PATCH 08/12] lib/repo: Include WKD update URLs in GPG key listing If the key UID contains a valid email address, include the GPG WKD update URLs in GVariant returned by ostree_repo_remote_get_gpg_keys(). --- src/libostree/ostree-repo.c | 14 ++++++++++++++ src/libostree/ostree-repo.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index b20aa61793..5bd76e91f5 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2439,6 +2439,16 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, for (gpgme_user_id_t uid = key->uids; uid != NULL; uid = uid->next) { + /* Get WKD update URLs if address set */ + g_autofree char *advanced_url = NULL; + g_autofree char *direct_url = NULL; + if (uid->address != NULL) + { + if (!ot_gpg_wkd_urls (uid->address, &advanced_url, &direct_url, + error)) + return FALSE; + } + g_auto(GVariantDict) uid_dict = OT_VARIANT_BUILDER_INITIALIZER; g_variant_dict_init (&uid_dict, NULL); g_variant_dict_insert_value (&uid_dict, "uid", @@ -2453,6 +2463,10 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, g_variant_new_boolean (uid->revoked)); g_variant_dict_insert_value (&uid_dict, "invalid", g_variant_new_boolean (uid->invalid)); + g_variant_dict_insert_value (&uid_dict, "advanced_url", + g_variant_new ("ms", advanced_url)); + g_variant_dict_insert_value (&uid_dict, "direct_url", + g_variant_new ("ms", direct_url)); g_variant_builder_add (&uids_builder, "(@a{sv})", g_variant_dict_end (&uid_dict)); } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 7694d40c65..ecf9277200 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1449,6 +1449,8 @@ gboolean ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self, * - 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 + * - key: `advanced_url`, value: `ms`, advanced WKD update URL + * - key: `direct_url`, value: `ms`, direct WKD update URL * - a{sv} - Additional metadata dictionary. There are currently no * additional metadata keys defined. * From 90a3bda1f8298e149cb4940369cec4bdc4e53972 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 11:27:54 -0600 Subject: [PATCH 09/12] bin/remote: Include update URLs in list-gpg-keys --- src/ostree/ot-dump.c | 7 +++++++ tests/test-remote-list-gpg-keys.sh | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index 1c0f04a9dd..6db6dc8f14 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -488,6 +488,13 @@ ot_dump_gpg_key (GVariant *key, uid, revoked ? " (revoked)" : "", invalid ? " (invalid)" : ""); + + const char *advanced_url = NULL; + const char *direct_url = NULL; + (void) g_variant_lookup (uid_v, "advanced_url", "m&s", &advanced_url); + (void) g_variant_lookup (uid_v, "direct_url", "m&s", &direct_url); + g_print (" Advanced update URL: %s\n", advanced_url ?: ""); + g_print (" Direct update URL: %s\n", direct_url ?: ""); } GVariant *subkey = NULL; diff --git a/tests/test-remote-list-gpg-keys.sh b/tests/test-remote-list-gpg-keys.sh index 5ad6c9f2e1..81699f14ec 100755 --- a/tests/test-remote-list-gpg-keys.sh +++ b/tests/test-remote-list-gpg-keys.sh @@ -67,6 +67,8 @@ cat > expected <<"EOF" Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA Created: Tue Sep 10 02:29:42 2013 UID: Ostree Tester + Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test + Direct update URL: https://test.com/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 Created: Tue Sep 10 02:29:42 2013 EOF @@ -89,16 +91,22 @@ cat > expected <<"EOF" Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA Created: Tue Sep 10 02:29:42 2013 UID: Ostree Tester + Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test + Direct update URL: https://test.com/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 Created: Tue Sep 10 02:29:42 2013 Key: 7B3B1020D74479687FDB2273D8228CFECA950D41 Created: Tue Mar 17 14:00:32 2015 UID: Ostree Tester II + Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/nnxwsxno46ap6hw7fgphp68j76egpfa9?l=test2 + Direct update URL: https://test.com/.well-known/openpgpkey/hu/nnxwsxno46ap6hw7fgphp68j76egpfa9?l=test2 Subkey: 1EFA95C06EB1EB91754575E004B69C2560D53993 Created: Tue Mar 17 14:00:32 2015 Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67 Created: Tue Mar 17 14:01:05 2015 UID: Ostree Tester III + Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/8494gyqhmrcs6gn38tn6kgjexet117cj?l=test3 + Direct update URL: https://test.com/.well-known/openpgpkey/hu/8494gyqhmrcs6gn38tn6kgjexet117cj?l=test3 Subkey: 0E45E48CBF7B360C0E04443E0C601A7402416340 Created: Tue Mar 17 14:01:05 2015 EOF From 30c054b521920a1c30f2530eabc2811230d2f97d Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 15 Jul 2021 16:24:36 -0600 Subject: [PATCH 10/12] fixup! lib/repo: Add ostree_repo_remote_get_gpg_keys() --- src/libostree/ostree-repo.c | 10 +++++----- src/libostree/ostree-repo.h | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 5bd76e91f5..d4c5044072 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2413,9 +2413,9 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, 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_variant_builder_init (&subkeys_builder, G_VARIANT_TYPE ("aa{sv}")); g_auto(GVariantBuilder) uids_builder = OT_VARIANT_BUILDER_INITIALIZER; - g_variant_builder_init (&uids_builder, G_VARIANT_TYPE ("a(a{sv})")); + g_variant_builder_init (&uids_builder, G_VARIANT_TYPE ("aa{sv}")); for (gpgme_subkey_t subkey = key->subkeys; subkey != NULL; subkey = subkey->next) { @@ -2433,7 +2433,7 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, 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_builder_add (&subkeys_builder, "@a{sv}", g_variant_dict_end (&subkey_dict)); } @@ -2467,7 +2467,7 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, g_variant_new ("ms", advanced_url)); g_variant_dict_insert_value (&uid_dict, "direct_url", g_variant_new ("ms", direct_url)); - g_variant_builder_add (&uids_builder, "(@a{sv})", + g_variant_builder_add (&uids_builder, "@a{sv}", g_variant_dict_end (&uid_dict)); } @@ -2475,7 +2475,7 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, 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})", + GVariant *key_variant = g_variant_new ("(@aa{sv}@aa{sv}@a{sv})", g_variant_builder_end (&subkeys_builder), g_variant_builder_end (&uids_builder), g_variant_dict_end (&metadata_dict)); diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index ecf9277200..5f3093dff3 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1429,7 +1429,7 @@ gboolean ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self, /** * OSTREE_GPG_KEY_GVARIANT_FORMAT: * - * - a(a{sv}) - Array of subkeys. Each a{sv} dictionary represents a + * - aa{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 @@ -1441,7 +1441,7 @@ gboolean ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self, * - 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 + * - aa{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 @@ -1456,7 +1456,7 @@ gboolean ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self, * * Since: 2021.4 */ -#define OSTREE_GPG_KEY_GVARIANT_STRING "(a(a{sv})a(a{sv})a{sv})" +#define OSTREE_GPG_KEY_GVARIANT_STRING "(aa{sv}aa{sv}a{sv})" #define OSTREE_GPG_KEY_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_GPG_KEY_GVARIANT_STRING) _OSTREE_PUBLIC From 814e481fffa58a5b4417c8162fa4dd60db5fa3d4 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 15 Jul 2021 16:25:13 -0600 Subject: [PATCH 11/12] fixup! bin/remote: Add list-gpg-keys subcommand --- src/ostree/ot-dump.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index 6db6dc8f14..05c0978026 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -468,7 +468,7 @@ ot_dump_gpg_key (GVariant *key, g_variant_iter_init (&subkeys_iter, subkeys_v); g_autoptr(GVariant) primary_key = NULL; - g_variant_iter_next (&subkeys_iter, "(@a{sv})", &primary_key); + g_variant_iter_next (&subkeys_iter, "@a{sv}", &primary_key); if (!dump_gpg_subkey (primary_key, TRUE, error)) return FALSE; @@ -476,7 +476,7 @@ ot_dump_gpg_key (GVariant *key, GVariantIter uids_iter; g_variant_iter_init (&uids_iter, uids_v); GVariant *uid_v = NULL; - while (g_variant_iter_loop (&uids_iter, "(@a{sv})", &uid_v)) + while (g_variant_iter_loop (&uids_iter, "@a{sv}", &uid_v)) { const gchar *uid = NULL; gboolean revoked = FALSE; @@ -498,7 +498,7 @@ ot_dump_gpg_key (GVariant *key, } GVariant *subkey = NULL; - while (g_variant_iter_loop (&subkeys_iter, "(@a{sv})", &subkey)) + while (g_variant_iter_loop (&subkeys_iter, "@a{sv}", &subkey)) { if (!dump_gpg_subkey (subkey, FALSE, error)) return FALSE; From 81df5c8abae137bad8a08acefeab7b3a8e7205c1 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 15 Jul 2021 17:03:45 -0600 Subject: [PATCH 12/12] fixup! lib/repo: Add ostree_repo_remote_get_gpg_keys() --- src/libostree/ostree-repo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index d4c5044072..0ff2c53f68 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2366,7 +2366,7 @@ _ostree_repo_gpg_prepare_verifier (OstreeRepo *self, /** * ostree_repo_remote_get_gpg_keys: * @self: an #OstreeRepo - * @name (nullable): name of the remote or %NULL + * @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):