From e9a0ebb721d6aa0b49a5f7aa0e8002911fd58b03 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Fri, 26 Jul 2019 09:38:23 -0600 Subject: [PATCH 01/15] 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 | 111 ++++++++++++++++------------ 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/src/libostree/ostree-gpg-verifier.c b/src/libostree/ostree-gpg-verifier.c index 95ed36eed6..c2b3ea9e21 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,47 +135,89 @@ _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); + 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; if (!glnx_openat_rdonly (AT_FDCWD, path, TRUE, &fd, error)) - goto out; + return FALSE; gpg_error = gpgme_data_new_from_fd (&kdata, fd); if (gpg_error != GPG_ERR_NO_ERROR) { ot_gpgme_throw (gpg_error, error, "Loading data from fd %i", fd); - goto out; + return FALSE; } - 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"); - goto out; + return FALSE; } } } - gpgme_set_armor (result->context, armor); + gpgme_set_armor (gpgme_ctx, armor); + + return TRUE; +} + +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 dff3c244bef9e6199d851b8466eab82b14579346 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 10:36:11 -0600 Subject: [PATCH 02/15] 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 584037c428..1f35a71d73 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -5268,28 +5268,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) { @@ -5299,11 +5296,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) { @@ -5319,14 +5316,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; } } @@ -5334,20 +5331,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 7397b18778227eb99d4949a01ef82ce68203b6b2 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 13:36:00 -0600 Subject: [PATCH 03/15] 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. --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 2 + src/libostree/ostree-gpg-verifier.c | 85 +++++++++++++++++++ src/libostree/ostree-gpg-verifier.h | 6 ++ src/libostree/ostree-repo.c | 123 ++++++++++++++++++++++++++++ src/libostree/ostree-repo.h | 40 +++++++++ 6 files changed, 257 insertions(+) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 252a563acb..dfc998443b 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -312,6 +312,7 @@ ostree_repo_remote_get_url ostree_repo_remote_get_gpg_verify ostree_repo_remote_get_gpg_verify_summary ostree_repo_remote_gpg_import +ostree_repo_remote_get_gpg_keys ostree_repo_remote_fetch_summary ostree_repo_remote_fetch_summary_with_options ostree_repo_reload_config diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 0b876f3b02..8b1cde2ba5 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -19,6 +19,8 @@ /* Add new symbols here. Release commits should copy this section into -released.sym. */ LIBOSTREE_2019.5 { +global: + ostree_repo_remote_get_gpg_keys; } LIBOSTREE_2019.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-gpg-verifier.c b/src/libostree/ostree-gpg-verifier.c index c2b3ea9e21..eef0a48ff2 100644 --- a/src/libostree/ostree-gpg-verifier.c +++ b/src/libostree/ostree-gpg-verifier.c @@ -181,6 +181,91 @@ _ostree_gpg_verifier_import_keys (OstreeGpgVerifier *self, return TRUE; } +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 1f35a71d73..f0a216aa79 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2349,6 +2349,129 @@ 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, + OstreeGpgVerifier **out_verifier, + GCancellable *cancellable, + GError **error); + +/** + * ostree_repo_remote_get_gpg_keys: + * @self: an #OstreeRepo + * @name: name of the remote + * @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. 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: 2019.5 + */ +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; + if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, &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_autoptr(GVariantBuilder) subkeys_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(a{sv})")); + g_autoptr(GVariantBuilder) uids_builder = g_variant_builder_new (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_autoptr(GVariantDict) metadata_dict = g_variant_dict_new (NULL); + + g_autoptr(GVariant) key_variant = + g_variant_ref_sink (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_steal_pointer (&key_variant)); + } + + if (out_keys) + *out_keys = g_steal_pointer (&keys); + + return TRUE; +#else /* OSTREE_DISABLE_GPGME */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "'%s': GPG feature is disabled in a build time", + __FUNCTION__); + return FALSE; +#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 40d3f77379..d1503f2f20 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1347,6 +1347,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: 2019.5 + */ +#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 a12242913f4686d94e3ddbc36eceb4a828d12bcd Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 13 Aug 2019 10:10:50 -0600 Subject: [PATCH 04/15] 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 the GPG key updates have been applied. --- Makefile-ostree.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 | 72 ++++++++++++ src/ostree/ot-remote-builtins.h | 1 + 8 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 src/ostree/ot-remote-builtin-list-gpg-keys.c diff --git a/Makefile-ostree.am b/Makefile-ostree.am index 7b53cb1489..9708889805 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -109,6 +109,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/bash/ostree b/bash/ostree index fc42998376..c75cb5639a 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1232,6 +1232,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 @@ -1346,6 +1380,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 38f3730b84..ccc312edcc 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; @@ -123,7 +136,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 */ @@ -366,3 +379,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..31a35fc61f --- /dev/null +++ b/src/ostree/ot-remote-builtin-list-gpg-keys.c @@ -0,0 +1,72 @@ +/* + * 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; + + if (argc < 2) + { + ot_util_usage_error (context, "NAME must be specified", error); + return FALSE; + } + + const char *remote_name = argv[1]; + + 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); From 655f738096795c27cf9a4c009ac9769e9e399d12 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Thu, 15 Aug 2019 21:38:23 -0600 Subject: [PATCH 05/15] add tests for listing GPG keys --- tests/test-remote-gpg-import.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test-remote-gpg-import.sh b/tests/test-remote-gpg-import.sh index 4d73fa1164..fd7d9b09f6 100755 --- a/tests/test-remote-gpg-import.sh +++ b/tests/test-remote-gpg-import.sh @@ -92,6 +92,18 @@ ${OSTREE} remote add R1 $(cat httpd-address)/ostree/gnomerepo cat ${test_tmpdir}/gpghome/key{1,2,3}.asc | ${OSTREE} remote gpg-import --stdin R1 | grep -o 'Imported [[:digit:]] GPG key' > result assert_file_has_content result 'Imported 3 GPG key' +# List out keys +${OSTREE} remote list-gpg-keys R1 > result +assert_file_has_content result 'Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA' +assert_file_has_content result 'UID: Ostree Tester ' +assert_file_has_content result 'Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49' +assert_file_has_content result 'Key: 7B3B1020D74479687FDB2273D8228CFECA950D41' +assert_file_has_content result 'UID: Ostree Tester II ' +assert_file_has_content result 'Subkey: 1EFA95C06EB1EB91754575E004B69C2560D53993' +assert_file_has_content result 'Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67' +assert_file_has_content result 'UID: Ostree Tester III ' +assert_file_has_content result 'Subkey: 0E45E48CBF7B360C0E04443E0C601A7402416340' + ${OSTREE} remote delete R1 #------------------------------------------------------------ From da1b903983d59cf3a4c009f09fb9b5d13057f3aa Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 10:09:57 -0600 Subject: [PATCH 06/15] 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 e8077140d17b7ff75a38005f430350ab71c28cdf Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 27 Aug 2019 10:28:10 -0600 Subject: [PATCH 07/15] 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://tools.ietf.org/html/draft-koch-openpgp-webkey-service-08#section-3.1 --- src/libotutil/ot-gpg-utils.c | 90 ++++++++++++++++++++++++++++++++++++ src/libotutil/ot-gpg-utils.h | 5 ++ 2 files changed, 95 insertions(+) diff --git a/src/libotutil/ot-gpg-utils.c b/src/libotutil/ot-gpg-utils.c index 743d941e37..476222167e 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,92 @@ ot_gpgme_kill_agent (const char *homedir) return; } } + +static char * +ascii_lower (const char *in) +{ + GString *tmp; + + g_return_val_if_fail (in != NULL, NULL); + tmp = g_string_new (in); + return g_string_free (g_string_ascii_down (tmp), FALSE); +} + +/* 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_autoptr(GChecksum) checksum = NULL; + guint8 digest[20] = { 0 }; + gsize len = sizeof(digest); + char *encoded; + + g_return_val_if_fail (local != NULL, NULL); + + checksum = g_checksum_new (G_CHECKSUM_SHA1); + g_checksum_update (checksum, (const guchar *)local, -1); + g_checksum_get_digest (checksum, digest, &len); + + 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://tools.ietf.org/html/draft-koch-openpgp-webkey-service-08#section-3.1. + */ +gboolean +ot_gpg_wkd_urls (const char *email, + char **out_advanced_url, + char **out_direct_url, + GError **error) +{ + g_auto(GStrv) email_parts = NULL; + g_autofree char *local_lowered = NULL; + g_autofree char *domain_lowered = NULL; + g_autofree char *local_encoded = NULL; + g_autofree char *local_escaped = NULL; + g_autofree char *advanced_url = NULL; + g_autofree char *direct_url = NULL; + + g_return_val_if_fail (email != NULL, FALSE); + + 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; + } + + local_lowered = ascii_lower (email_parts[0]); + domain_lowered = ascii_lower (email_parts[1]); + local_encoded = encode_wkd_local (local_lowered); + local_escaped = g_uri_escape_string (email_parts[0], NULL, FALSE); + + 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 ("Advanced WKD URL: %s", advanced_url); + + direct_url = g_strdup_printf ("https://%s/.well-known/openpgpkey/hu/%s?l=%s", + email_parts[1], local_encoded, local_escaped); + g_debug ("Direct WKD URL: %s", 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 6e055833025d08b6210b12acdaee93338e8b959d Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 11:15:25 -0600 Subject: [PATCH 08/15] 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 f0a216aa79..4143ed5a7d 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2431,6 +2431,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", @@ -2445,6 +2455,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 d1503f2f20..a4df14e79d 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1371,6 +1371,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 932a556288dd5094b076b58df736db841ebb9d37 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 26 Aug 2019 11:27:54 -0600 Subject: [PATCH 09/15] bin/remote: Include update URLs in list-gpg-keys --- src/ostree/ot-dump.c | 7 +++++++ tests/test-remote-gpg-import.sh | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index ccc312edcc..1027149282 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -464,6 +464,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-gpg-import.sh b/tests/test-remote-gpg-import.sh index fd7d9b09f6..e64f10082c 100755 --- a/tests/test-remote-gpg-import.sh +++ b/tests/test-remote-gpg-import.sh @@ -96,12 +96,18 @@ assert_file_has_content result 'Imported 3 GPG key' ${OSTREE} remote list-gpg-keys R1 > result assert_file_has_content result 'Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA' assert_file_has_content result 'UID: Ostree Tester ' +assert_file_has_content result 'Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test$' +assert_file_has_content result 'Direct update URL: https://test.com/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test$' assert_file_has_content result 'Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49' assert_file_has_content result 'Key: 7B3B1020D74479687FDB2273D8228CFECA950D41' assert_file_has_content result 'UID: Ostree Tester II ' +assert_file_has_content result 'Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/nnxwsxno46ap6hw7fgphp68j76egpfa9?l=test2$' +assert_file_has_content result 'Direct update URL: https://test.com/.well-known/openpgpkey/hu/nnxwsxno46ap6hw7fgphp68j76egpfa9?l=test2$' assert_file_has_content result 'Subkey: 1EFA95C06EB1EB91754575E004B69C2560D53993' assert_file_has_content result 'Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67' assert_file_has_content result 'UID: Ostree Tester III ' +assert_file_has_content result 'Advanced update URL: https://openpgpkey.test.com/.well-known/openpgpkey/test.com/hu/8494gyqhmrcs6gn38tn6kgjexet117cj?l=test3$' +assert_file_has_content result 'Direct update URL: https://test.com/.well-known/openpgpkey/hu/8494gyqhmrcs6gn38tn6kgjexet117cj?l=test3$' assert_file_has_content result 'Subkey: 0E45E48CBF7B360C0E04443E0C601A7402416340' ${OSTREE} remote delete R1 From 7bac5b81f2f901b74a1a2f3adad04a81867bbb3a Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Fri, 30 Aug 2019 12:40:39 -0600 Subject: [PATCH 10/15] wip filter keyring by email address --- src/libotutil/ot-gpg-utils.c | 112 +++++++++++++++++++++++++++++++++++ src/libotutil/ot-gpg-utils.h | 7 +++ 2 files changed, 119 insertions(+) diff --git a/src/libotutil/ot-gpg-utils.c b/src/libotutil/ot-gpg-utils.c index 476222167e..c4076f718a 100644 --- a/src/libotutil/ot-gpg-utils.c +++ b/src/libotutil/ot-gpg-utils.c @@ -540,6 +540,118 @@ ot_gpgme_kill_agent (const char *homedir) } } +gboolean +ot_gpgme_filter_keyring_by_email (GBytes *keyring_data, + const char *email, + gpgme_data_t export_data, + GPtrArray *export_fingerprints, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *tmp_dir = NULL; + g_autoptr(GPtrArray) export_keys = NULL; + + g_return_val_if_fail (keyring_data != NULL, FALSE); + g_return_val_if_fail (email != NULL, FALSE); + g_return_val_if_fail (export_data != NULL, FALSE); + + /* Setup a temporary context and homedir to import the keyring into since + * gpgme offers no other method to analyze it. + */ + g_auto(gpgme_ctx_t) ctx = ot_gpgme_new_ctx (NULL, error); + if (ctx == NULL) + goto out; + if (!ot_gpgme_ctx_tmp_home_dir (ctx, &tmp_dir, NULL, cancellable, error)) + goto out; + + /* Import the keyring data */ + gpgme_error_t gpg_error = 0; + g_auto(gpgme_data_t) input_buffer = NULL; + gpg_error = gpgme_data_new_from_mem (&input_buffer, + g_bytes_get_data (keyring_data, NULL), + g_bytes_get_size (keyring_data), + 0 /* do not copy */); + if (gpg_error != GPG_ERR_NO_ERROR) + { + ot_gpgme_throw (gpg_error, error, "Unable to load keyring data"); + goto out; + } + gpg_error = gpgme_op_import (ctx, input_buffer); + if (gpg_error != GPG_ERR_NO_ERROR) + { + ot_gpgme_throw (gpg_error, error, "Unable to import keyring data"); + goto out; + } + + /* Fail if any of the keys couldn't be imported */ + gpgme_import_result_t import_result; + gpgme_import_status_t import_status; + import_result = gpgme_op_import_result (ctx); + g_debug ("Read %d keys for %s", import_result->imported, email); + for (import_status = import_result->imports; + import_status != NULL; + import_status = import_status->next) + { + if (import_status->result != GPG_ERR_NO_ERROR) + { + ot_gpgme_throw (import_status->result, error, + "Unable to import key \"%s\"", + import_status->fpr); + goto out; + } + } + + /* Iterate through the imported keys looking for any that match email */ + export_keys = g_ptr_array_new_with_free_func ((GDestroyNotify) gpgme_key_unref); + gpg_error = gpgme_op_keylist_start (ctx, NULL, 0); + while (gpg_error == GPG_ERR_NO_ERROR) + { + g_auto(gpgme_key_t) key = NULL; + + gpg_error = gpgme_op_keylist_next (ctx, &key); + if (gpg_error != GPG_ERR_NO_ERROR) + break; + + for (gpgme_user_id_t uid = key->uids; uid != NULL; uid = uid->next) + { + if (g_strcmp0 (uid->address, email) == 0) + { + g_debug ("Found key %s matching %s", key->fpr, email); + gpgme_key_ref (key); + g_ptr_array_add (export_keys, key); + g_ptr_array_add (export_fingerprints, g_strdup (key->fpr)); + break; + } + } + } + + /* Export the matching keys */ + if (export_keys->len > 0) + { + /* NULL terminate key array */ + g_ptr_array_add (export_keys, NULL); + + gpg_error = gpgme_op_export_keys (ctx, (gpgme_key_t *) export_keys->pdata, + 0, export_data); + if (gpg_error != GPG_ERR_NO_ERROR) + { + ot_gpgme_throw (gpg_error, error, "Unable to export keys"); + goto out; + } + } + + 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; +} + static char * ascii_lower (const char *in) { diff --git a/src/libotutil/ot-gpg-utils.h b/src/libotutil/ot-gpg-utils.h index b559b69573..6d75bb5dbb 100644 --- a/src/libotutil/ot-gpg-utils.h +++ b/src/libotutil/ot-gpg-utils.h @@ -48,6 +48,13 @@ gpgme_ctx_t ot_gpgme_new_ctx (const char *homedir, void ot_gpgme_kill_agent (const char *homedir); +gboolean ot_gpgme_filter_keyring_by_email (GBytes *keyring_data, + const char *email, + gpgme_data_t export_data, + GPtrArray *export_fingerprints, + GCancellable *cancellable, + GError **error); + gboolean ot_gpg_wkd_urls (const char *email, char **out_advanced_url, char **out_direct_url, From 0f5d5ee90b5a21eb6ffa647a1489496c81b7bd55 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 27 Aug 2019 10:16:07 -0600 Subject: [PATCH 11/15] wip GPG key update API --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 1 + src/libostree/ostree-repo.c | 199 ++++++++++++++++++++++++++++++ src/libostree/ostree-repo.h | 7 ++ 4 files changed, 208 insertions(+) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index dfc998443b..398292d0cb 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -313,6 +313,7 @@ ostree_repo_remote_get_gpg_verify ostree_repo_remote_get_gpg_verify_summary ostree_repo_remote_gpg_import ostree_repo_remote_get_gpg_keys +ostree_repo_remote_update_gpg_keys ostree_repo_remote_fetch_summary ostree_repo_remote_fetch_summary_with_options ostree_repo_reload_config diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 8b1cde2ba5..0b8d7a0583 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -21,6 +21,7 @@ LIBOSTREE_2019.5 { global: ostree_repo_remote_get_gpg_keys; + ostree_repo_remote_update_gpg_keys; } LIBOSTREE_2019.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 4143ed5a7d..797d9311c8 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -40,6 +40,7 @@ #include "ostree-repo-file-enumerator.h" #include "ostree-gpg-verifier.h" #include "ostree-repo-static-delta-private.h" +#include "ostree-fetcher-util.h" #include "ot-fs-utils.h" #include "ostree-autocleanups.h" @@ -2486,6 +2487,204 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, #endif /* OSTREE_DISABLE_GPGME */ } +/* Arbitrary limits for fetched GPG keys */ +#define GPG_UPDATE_MAX_SIZE (1024 * 1024) +#define GPG_UPDATE_N_RETRIES 1 + +static gboolean +fetch_gpg_uid_key (OstreeFetcher *fetcher, + const char *address, + GBytes **out_key, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (address != NULL, FALSE); + + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + return FALSE; + + g_autofree char *advanced_url = NULL; + g_autofree char *direct_url = NULL; + if (!ot_gpg_wkd_urls (address, &advanced_url, &direct_url, error)) + return FALSE; + + g_autoptr(OstreeFetcherURI) advanced_uri = + _ostree_fetcher_uri_parse (advanced_url, error); + if (advanced_uri == NULL) + return FALSE; + + g_autoptr(GError) local_error = NULL; + g_autoptr(GBytes) key = NULL; + if (!_ostree_fetcher_request_uri_to_membuf (fetcher, + advanced_uri, + 0, + GPG_UPDATE_N_RETRIES, + &key, + GPG_UPDATE_MAX_SIZE, + cancellable, + &local_error)) + { + if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + /* Key at advanced URL not found, try direct URL */ + g_autoptr(OstreeFetcherURI) direct_uri = + _ostree_fetcher_uri_parse (direct_url, error); + if (direct_uri == NULL) + return FALSE; + if (!_ostree_fetcher_request_uri_to_membuf (fetcher, + direct_uri, + 0, + GPG_UPDATE_N_RETRIES, + &key, + GPG_UPDATE_MAX_SIZE, + cancellable, + error)) + return FALSE; + } + + if (out_key != NULL) + *out_key = g_steal_pointer (&key); + + return TRUE; +} + +/** + * ostree_repo_remote_update_gpg_keys: + * @self: an #OstreeRepo + * @name: name of the remote + * @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 + * + * Update the trusted GPG keys for the remote @name. The updated keys will be + * returned in the @out_keys #GPtrArray. Each element in the array is a + * #GVariant of format %OSTREE_GPG_KEY_GVARIANT_FORMAT. + * + * Returns: %TRUE if the GPG keys could be updated, %FALSE otherwise + * + * Since: 2019.5 + */ +gboolean +ostree_repo_remote_update_gpg_keys (OstreeRepo *self, + const char *name, + GPtrArray **out_keys, + GCancellable *cancellable, + GError **error) +{ +#ifndef OSTREE_DISABLE_GPGME + g_autoptr(OstreeGpgVerifier) verifier = NULL; + if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, &verifier, + cancellable, error)) + return FALSE; + + g_autoptr(GPtrArray) gpg_keys = NULL; + if (!_ostree_gpg_verifier_list_keys (verifier, NULL, &gpg_keys, cancellable, + error)) + return FALSE; + + /* Use a temporary file for the updated keys */ + g_auto(GLnxTmpfile) updated_keys_tmpf = { 0, }; + if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &updated_keys_tmpf, + error)) + return FALSE; + + /* GPGME buffer for updated keys */ + g_autoptr(GOutputStream) updated_keys_ostream = + g_unix_output_stream_new (updated_keys_tmpf.fd, FALSE); + g_auto(gpgme_data_t) updated_keys_data = ot_gpgme_data_output (updated_keys_ostream); + + g_autoptr(GPtrArray) updated_fingerprints = g_ptr_array_new_with_free_func (g_free); + g_autoptr(OstreeFetcher) fetcher = _ostree_fetcher_new (self->tmp_dir_fd, + name, 0); + for (guint i = 0; i < gpg_keys->len; i++) + { + gpgme_key_t key = gpg_keys->pdata[i]; + + for (gpgme_user_id_t uid = key->uids; uid != NULL; uid = uid->next) + { + if (uid->address == NULL) + continue; + + g_autoptr(GBytes) fetched_key = NULL; + g_autoptr(GError) temp_error = NULL; + if (!fetch_gpg_uid_key (fetcher, uid->address, &fetched_key, + cancellable, &temp_error)) + { + if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + /* No key found for this uid */ + g_debug ("No GPG key update found for UID %s", uid->uid); + g_clear_error (&temp_error); + continue; + } else { + g_propagate_error (error, g_steal_pointer (&temp_error)); + return FALSE; + } + } + + /* Find the keys matching this email */ + if (!ot_gpgme_filter_keyring_by_email (fetched_key, + uid->address, + updated_keys_data, + updated_fingerprints, + cancellable, + error)) + return FALSE; + } + } + + /* Writing to the new keyring is finished */ + gpgme_data_release (updated_keys_data); + updated_keys_data = NULL; + g_clear_object (&updated_keys_ostream); + + /* Import the updated keys if any were found */ + g_autoptr(GPtrArray) ret_keys = NULL; + if (updated_fingerprints->len > 0) + { + /* NULL terminate the fingerprint array for use as a key ID array */ + g_ptr_array_add (updated_fingerprints, NULL); + const char * const *key_ids = (const char * const *)updated_fingerprints->pdata; + + /* Seek back to the beginning of the tmp file and open an input + * stream for importing. + */ + if (lseek (updated_keys_tmpf.fd, 0, SEEK_SET) < 0) + return glnx_throw_errno_prefix (error, "lseek"); + g_autoptr(GInputStream) updated_keys_istream = + g_unix_input_stream_new (updated_keys_tmpf.fd, FALSE); + if (!ostree_repo_remote_gpg_import (self, name, updated_keys_istream, + key_ids, NULL, cancellable, error)) + return FALSE; + + if (!ostree_repo_remote_get_gpg_keys (self, name, key_ids, &ret_keys, + cancellable, error)) + return FALSE; + } + else + { + /* Empty key array */ + ret_keys = g_ptr_array_new (); + } + + if (out_keys != NULL) + *out_keys = g_steal_pointer (&ret_keys); + + return TRUE; +#else /* OSTREE_DISABLE_GPGME */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "'%s': GPG feature is disabled in a build time", + __FUNCTION__); + return FALSE; +#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 a4df14e79d..cf8c8d99c4 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1389,6 +1389,13 @@ gboolean ostree_repo_remote_get_gpg_keys (OstreeRepo *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_repo_remote_update_gpg_keys (OstreeRepo *self, + const char *name, + GPtrArray **out_keys, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC gboolean ostree_repo_remote_gpg_import (OstreeRepo *self, const char *name, From 2cf398576298960ee5b405125ebaa7f4226fd82a Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 21 Oct 2019 10:44:21 -0600 Subject: [PATCH 12/15] bin/remote: Add update-gpg-keys subcommand This provides a wrapper for the `ostree_repo_remote_update_gpg_keys` API to update a remote's GPG trusted keys using the PGP Web Key Directory protocol. --- Makefile-ostree.am | 1 + bash/ostree | 1 + man/ostree-remote.xml | 9 +++ src/ostree/ot-builtin-remote.c | 3 + .../ot-remote-builtin-update-gpg-keys.c | 72 +++++++++++++++++++ src/ostree/ot-remote-builtins.h | 1 + 6 files changed, 87 insertions(+) create mode 100644 src/ostree/ot-remote-builtin-update-gpg-keys.c diff --git a/Makefile-ostree.am b/Makefile-ostree.am index 9708889805..0eaed26edc 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -110,6 +110,7 @@ if USE_GPGME ostree_SOURCES += \ src/ostree/ot-remote-builtin-gpg-import.c \ src/ostree/ot-remote-builtin-list-gpg-keys.c \ + src/ostree/ot-remote-builtin-update-gpg-keys.c \ $(NULL) endif diff --git a/bash/ostree b/bash/ostree index c75cb5639a..8b4f9b3083 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1384,6 +1384,7 @@ _ostree_remote() { refs show-url summary + update-gpg-keys " __ostree_subcommands "$subcommands" && return 0 diff --git a/man/ostree-remote.xml b/man/ostree-remote.xml index 928bf9b5f8..8d348a8230 100644 --- a/man/ostree-remote.xml +++ b/man/ostree-remote.xml @@ -68,6 +68,9 @@ Boston, MA 02111-1307, USA. ostree remote list-gpg-keys NAME + + ostree remote update-gpg-keys NAME + ostree remote refs NAME @@ -118,6 +121,12 @@ Boston, MA 02111-1307, USA. 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. + + The update-gpg-keys subcommand will attempt to + update the remote's GPG trusted keys using the PGP Web Key Directory + protocol. The URLs that will be used for locating keys can be seen in + the list-gpg-keys subcommand. + The various cookie related command allow management of a remote specific cookie jar. diff --git a/src/ostree/ot-builtin-remote.c b/src/ostree/ot-builtin-remote.c index 7028eacc75..d387db6dfe 100644 --- a/src/ostree/ot-builtin-remote.c +++ b/src/ostree/ot-builtin-remote.c @@ -47,6 +47,9 @@ static OstreeCommand remote_subcommands[] = { { "list-gpg-keys", OSTREE_BUILTIN_FLAG_NONE, ot_remote_builtin_list_gpg_keys, "Show remote GPG keys" }, + { "update-gpg-keys", OSTREE_BUILTIN_FLAG_NONE, + ot_remote_builtin_update_gpg_keys, + "Update remote GPG keys" }, #endif /* OSTREE_DISABLE_GPGME */ #ifdef HAVE_LIBCURL_OR_LIBSOUP { "add-cookie", OSTREE_BUILTIN_FLAG_NONE, diff --git a/src/ostree/ot-remote-builtin-update-gpg-keys.c b/src/ostree/ot-remote-builtin-update-gpg-keys.c new file mode 100644 index 0000000000..4488a75476 --- /dev/null +++ b/src/ostree/ot-remote-builtin-update-gpg-keys.c @@ -0,0 +1,72 @@ +/* + * 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_update_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; + + if (argc < 2) + { + ot_util_usage_error (context, "NAME must be specified", error); + return FALSE; + } + + const char *remote_name = argv[1]; + + g_autoptr(GPtrArray) keys = NULL; + if (!ostree_repo_remote_update_gpg_keys (repo, remote_name, &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 4b46af199f..3a04af450c 100644 --- a/src/ostree/ot-remote-builtins.h +++ b/src/ostree/ot-remote-builtins.h @@ -33,6 +33,7 @@ BUILTINPROTO(add); BUILTINPROTO(delete); BUILTINPROTO(gpg_import); BUILTINPROTO(list_gpg_keys); +BUILTINPROTO(update_gpg_keys); BUILTINPROTO(list); #ifdef HAVE_LIBCURL_OR_LIBSOUP BUILTINPROTO(add_cookie); From 7a9aca9552beab550a9d5b263fd60f0aba390f0a Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 21 Oct 2019 15:11:45 -0600 Subject: [PATCH 13/15] lib/gpg: Allow local server override for WKD URLs In order to test `ostree_remote_update_gpg_keys`, we need to be able to fetch the keys from a local test server. This inherently requires introducing a backdoor to the update process. If the _OSTREE_GPG_UPDATE_LOCAL_PORT environment variable is set, change the server to http://127.0.0.1: after validating that the port is numerical. This should keep any attack local by not allowing the URL to be changed to an arbitrary remote server. --- src/libotutil/ot-gpg-utils.c | 39 +++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/libotutil/ot-gpg-utils.c b/src/libotutil/ot-gpg-utils.c index c4076f718a..d5d45a5108 100644 --- a/src/libotutil/ot-gpg-utils.c +++ b/src/libotutil/ot-gpg-utils.c @@ -704,6 +704,8 @@ ot_gpg_wkd_urls (const char *email, g_autofree char *domain_lowered = NULL; g_autofree char *local_encoded = NULL; g_autofree char *local_escaped = NULL; + g_autofree char *advanced_server = NULL; + g_autofree char *direct_server = NULL; g_autofree char *advanced_url = NULL; g_autofree char *direct_url = NULL; @@ -722,15 +724,42 @@ ot_gpg_wkd_urls (const char *email, local_encoded = encode_wkd_local (local_lowered); local_escaped = g_uri_escape_string (email_parts[0], NULL, FALSE); - advanced_url = g_strdup_printf ("https://openpgpkey.%s" - "/.well-known/openpgpkey/" + /* Allow URLs to point to a local server for testing. */ + const char *local_port = g_getenv ("_OSTREE_GPG_UPDATE_LOCAL_PORT"); + if (local_port != NULL) + { + for (const char *cur = local_port; *cur != '\0'; cur++) + { + if (!g_ascii_isdigit (*cur)) + { + g_debug ("Ignoring non-digit environment variable " + "_OSTREE_GPG_UPDATE_LOCAL_PORT"); + local_port = NULL; + break; + } + } + } + + if (local_port != NULL) + { + advanced_server = g_strdup_printf ("http://127.0.0.1:%s", local_port); + direct_server = g_strdup (advanced_server); + } + else + { + advanced_server = g_strdup_printf ("https://openpgpkey.%s", + email_parts[1]); + direct_server = g_strdup_printf ("https://%s", email_parts[1]); + } + + advanced_url = g_strdup_printf ("%s/.well-known/openpgpkey/" "%s/hu/%s?l=%s", - email_parts[1], domain_lowered, + advanced_server, domain_lowered, local_encoded, local_escaped); g_debug ("Advanced WKD URL: %s", advanced_url); - direct_url = g_strdup_printf ("https://%s/.well-known/openpgpkey/hu/%s?l=%s", - email_parts[1], local_encoded, local_escaped); + direct_url = g_strdup_printf ("%s/.well-known/openpgpkey/hu/%s?l=%s", + direct_server, local_encoded, local_escaped); g_debug ("Direct WKD URL: %s", direct_url); if (out_advanced_url != NULL) From 27202db9fe22bc4bbdd2aa3225afdea819c4392d Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Mon, 21 Oct 2019 16:05:03 -0600 Subject: [PATCH 14/15] tests for update-gpg-keys --- Makefile-tests.am | 1 + tests/gpghome/key1-subkey-revoked.asc | 36 ++++++++++ tests/test-remote-update-gpg-keys.sh | 98 +++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 tests/gpghome/key1-subkey-revoked.asc create mode 100755 tests/test-remote-update-gpg-keys.sh diff --git a/Makefile-tests.am b/Makefile-tests.am index f5a6527811..401c59690d 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -140,6 +140,7 @@ _installed_or_uninstalled_test_scripts = \ if USE_GPGME _installed_or_uninstalled_test_scripts += \ tests/test-remote-gpg-import.sh \ + tests/test-remote-update-gpg-keys.sh \ tests/test-gpg-signed-commit.sh \ tests/test-admin-gpg.sh \ $(NULL) diff --git a/tests/gpghome/key1-subkey-revoked.asc b/tests/gpghome/key1-subkey-revoked.asc new file mode 100644 index 0000000000..8a7e65b3a2 --- /dev/null +++ b/tests/gpghome/key1-subkey-revoked.asc @@ -0,0 +1,36 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFIuhBYBCADTbnocQsJgMfOELkFt3wRrAZShijoBPYZT9BrIuIKZxAbaxZJr +Tbw8eIGgHZ51NCfdoikul0i82dt4hwtsACNVL5EGRmvTIKHPacb0yJMr1YBjcSwD +Slo+niLPb/oVtLTbDWFt/msYKREF/lGJT9dJyXkQ5UOwWdipDaHIlwb0IKUvL7cu +NpNthRFRm1M5d5M9OtqTCrCja6zckQ6OfvoStsbneHzfVWeH7vLcKBxxkfDhusVt +y1iVaDk1EYT8ZxsrAWw4S7nRK/bjr86IYpFPjG2aKMd9qxyIo7hcX4r8od24jzfM +v/ysOapnkTJuv8J6v7MakM1HkCz+TKF6gXxVABEBAAG0HU9zdHJlZSBUZXN0ZXIg +PHRlc3RAdGVzdC5jb20+iQE5BBMBAgAjBQJSLoQWAhsDBwsJCAcDAgEGFQgCCQoL +BBYCAwECHgECF4AACgkQf8oj2Ecs2vr/9wgAnme6WsWQy8CYeGH4q/5I6XFL6q1m +S0+qdeGnYRmR0jJAGJ84vqDhnKxjeQzp+8Nq81DHGEJBszCkMW2o22neFi2Mo95h +Dq3GWNZVldCDshjPs563AY6j7zACUN7Cy5XB3MK/vj5R/SrHBtJmSgPTx9WfmUgn +n5Udg+fzSsS8z8DUtJFtexgrSnEmwH+nOmIfrsjIYL5EPg+CTTalhygROrERjINr +pCYiShaFCKbuyt/XvyQ71y0JbB2yS7tDv0mL4SZjSuBQ1PkNE8ZQsymqBOJHA1Y3 +ppgPs1OenmtYgxaR8HQQv7uxHWZz0dmwQN93Qx8zMZwW40Odmdh1zLNQf7kBDQRS +LoQWAQgA9i9QWg28qmFrPIzn90ZlNlUtFzoZy/8/lIk34awge1uO5aHydYBzkuWU +jCDyBtQLWZQlwOKq8oHBbjENR2sfsmNkrYKcceQ02hSXqEJkc6jcDMCpB9eWy34K +sPZmdl76Eo/vIIgRqJ9JPeGoMPaIBg2ouEz6Ft6jcX3EriYIKebCEA9wPk29z40x +7D8mBZn06WrZ3JyePfbCdNJlQANEnrk7KDMNwPhhE1wcfPkiVtqBR0/FwIoUP0jn +PishIWOuFObYnXQQ2R8sxrw/V0hGqVTh+k+iNAjzEp4yPsAvB+LdMH9nCY5rU3Vo +1paEqVM1EHoBPu4NupRN0AjIJPr5UQARAQABiQFABCgBCgAqFiEEXmXedascUBhi +1HY0f8oj2Ecs2voFAl2uBq0MHQJXZSBsb3N0IGl0AAoJEH/KI9hHLNr6AM8H/1OF +IGKVihrk0/aqVzeB/qX0UEmy33BxUPQ1fW2Lwh0CorfgrkMfjoHUvWtj75Jmz+YR +YVmIPwIp7q46OlutFL6PcwT7AYEGlzf+EEqw82khToSarGFRrzcjiZ/XZoUPTXNF +7DPn6iya6QkU0nnbZATa0I7nPnVT5YPmJiEl1BIOWX23qkOhl8mgQ0wi7nsWi2Vp +wd2jTzfj5FYDC2qJYZp0kCPrTc6EtpVzx6/bwNerl8g94ViKEjMZnUOG28nqSmne +kMUh3hBRUJjvz88/xb+gHa73eJI731rcXkCySP0sAN4BHcRWZfJaj1OKAVFgXVx4 +9BC3GJvnjpe+M4uOCiSJAR8EGAECAAkFAlIuhBYCGwwACgkQf8oj2Ecs2vryLggA +x1z4SABo9kVZlxcUYF+Gc4pAUL+79boK7UmOohiQY7QfFKFJ8GTECuqWnfvDhhUf +htSS7qNrjbVt8YU0y0x9ePDaZTcdF1oN6c/o4a/aNiZiYW3rOLQllmG+LxkJwBBN +K1nYyzeHCy0IyIFc+ZgDspb0bOjglBIoJbmFogIZVJaXuSGfQ6SE5NUj27M2vv4u +FifaJv/KdJowp4jiFny/UcO5jRXUTre8U8YsUFM9qhE+meb3IGdcxaGttX3svp4S +h7t9q6tLI9wXXUsQULnHygQ2dsf7C0Bc5rJCjeWV34lFr0IkRmXxJN1FT2jY0XAX +xczwEQ5ae0xLxo2k+ggsLw== +=53Nf +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/test-remote-update-gpg-keys.sh b/tests/test-remote-update-gpg-keys.sh new file mode 100755 index 0000000000..f6f8fb14a5 --- /dev/null +++ b/tests/test-remote-update-gpg-keys.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# +# 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +# We don't want OSTREE_GPG_HOME used for these tests. +unset OSTREE_GPG_HOME + +setup_fake_remote_repo1 "archive" + +# Use the local http server for GPG key update tests +_OSTREE_GPG_UPDATE_LOCAL_PORT=$(cat ${test_tmpdir}/httpd-port) +export _OSTREE_GPG_UPDATE_LOCAL_PORT + +echo "1..6" + +cd ${test_tmpdir} +mkdir repo +ostree_repo_init repo + +# Check that update-gpg-keys works with no existing keys +${OSTREE} remote add R1 $(cat httpd-address)/ostree/gnomerepo +${OSTREE} remote update-gpg-keys R1 > result +assert_file_empty result +echo "ok remote with no gpg keys" + +# Import a GPG key and check that no updates found +${OSTREE} remote gpg-import --keyring ${test_tmpdir}/gpghome/key1.asc R1 +${OSTREE} remote update-gpg-keys R1 > result +assert_file_empty result +echo "ok update no keys found" + +# Test advanced update URL +rm -rf ${test_tmpdir}/httpd/.well-known/openpgpkey/ +mkdir -p ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/ +cp ${test_tmpdir}/gpghome/trusted/pubring.gpg \ + ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u +${OSTREE} remote update-gpg-keys R1 > result +assert_file_has_content result 'Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA' +assert_file_has_content result 'UID: Ostree Tester ' +assert_not_file_has_content result 'Key: 7B3B1020D74479687FDB2273D8228CFECA950D41' +assert_not_file_has_content result 'UID: Ostree Tester II ' +assert_not_file_has_content result 'Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67' +assert_not_file_has_content result 'UID: Ostree Tester III ' +echo "ok update advanced URL" + +# Test direct update URL +rm -rf ${test_tmpdir}/httpd/.well-known/openpgpkey/ +mkdir -p ${test_tmpdir}/httpd/.well-known/openpgpkey/hu/ +cp ${test_tmpdir}/gpghome/trusted/pubring.gpg \ + ${test_tmpdir}/httpd/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u +${OSTREE} remote update-gpg-keys R1 > result +assert_file_has_content result 'Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA' +assert_file_has_content result 'UID: Ostree Tester ' +assert_not_file_has_content result 'Key: 7B3B1020D74479687FDB2273D8228CFECA950D41' +assert_not_file_has_content result 'UID: Ostree Tester II ' +assert_not_file_has_content result 'Key: 7D29CF060B8269CDF63BFBDD0D15FAE7DF444D67' +assert_not_file_has_content result 'UID: Ostree Tester III ' +echo "ok update direct URL" + +# Test invalid remote GPG key +rm -rf ${test_tmpdir}/httpd/.well-known/openpgpkey/ +mkdir -p ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/ +echo invalid > ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u +${OSTREE} --verbose remote update-gpg-keys R1 > result +assert_file_empty result +echo "ok ignored invalid remote GPG key" + +# Test importing a revoked subkey +rm -rf ${test_tmpdir}/httpd/.well-known/openpgpkey/ +mkdir -p ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/ +cp ${test_tmpdir}/gpghome/key1-subkey-revoked.asc \ + ${test_tmpdir}/httpd/.well-known/openpgpkey/test.com/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u +${OSTREE} --verbose remote update-gpg-keys R1 > result +assert_file_has_content result 'Key: 5E65DE75AB1C501862D476347FCA23D8472CDAFA' +assert_file_has_content result 'UID: Ostree Tester ' +assert_file_has_content result 'Subkey: CC47B2DFB520AEF231180725DF20F58B408DEA49 (revoked)' +echo "ok update revoked subkey" From 6d46edfa9afa16b7dc708aec2520885729d237c4 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 19 Nov 2019 09:32:25 -0700 Subject: [PATCH 15/15] wip only show global keyrings when no remote supplied --- src/libostree/ostree-repo.c | 16 +++++++++------- src/ostree/ot-remote-builtin-list-gpg-keys.c | 12 ++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 797d9311c8..55dc90ccdf 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2355,6 +2355,7 @@ _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); @@ -2391,8 +2392,8 @@ ostree_repo_remote_get_gpg_keys (OstreeRepo *self, { #ifndef OSTREE_DISABLE_GPGME g_autoptr(OstreeGpgVerifier) verifier = NULL; - if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, &verifier, - cancellable, error)) + if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, (name == NULL), + &verifier, cancellable, error)) return FALSE; g_autoptr(GPtrArray) gpg_keys = NULL; @@ -2579,8 +2580,8 @@ ostree_repo_remote_update_gpg_keys (OstreeRepo *self, { #ifndef OSTREE_DISABLE_GPGME g_autoptr(OstreeGpgVerifier) verifier = NULL; - if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, &verifier, - cancellable, error)) + if (!_ostree_repo_gpg_prepare_verifier (self, name, NULL, NULL, (name == NULL), + &verifier, cancellable, error)) return FALSE; g_autoptr(GPtrArray) gpg_keys = NULL; @@ -5609,12 +5610,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) { @@ -5641,7 +5642,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; @@ -5663,7 +5664,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)) @@ -5702,6 +5703,7 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, remote_name, keyringdir, extra_keyring, + TRUE, &verifier, cancellable, error)) diff --git a/src/ostree/ot-remote-builtin-list-gpg-keys.c b/src/ostree/ot-remote-builtin-list-gpg-keys.c index 31a35fc61f..360d9e32af 100644 --- a/src/ostree/ot-remote-builtin-list-gpg-keys.c +++ b/src/ostree/ot-remote-builtin-list-gpg-keys.c @@ -49,13 +49,13 @@ ot_remote_builtin_list_gpg_keys (int argc, invocation, &repo, cancellable, error)) return FALSE; - if (argc < 2) - { - ot_util_usage_error (context, "NAME must be specified", error); - return FALSE; - } + /* if (argc < 2) */ + /* { */ + /* ot_util_usage_error (context, "NAME must be specified", error); */ + /* return FALSE; */ + /* } */ - const char *remote_name = argv[1]; + 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,