Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote GPG key info #2401

Merged
merged 12 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile-libostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 85 additions & 0 deletions src/libostree/ostree-gpg-verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +265 to +267
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably make these autocleanups too in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so. The gpgme code could use some sprucing up.

}

return ret;
}

OstreeGpgVerifyResult *
_ostree_gpg_verifier_check_signature (OstreeGpgVerifier *self,
GBytes *signed_data,
Expand Down
6 changes: 6 additions & 0 deletions src/libostree/ostree-gpg-verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
124 changes: 124 additions & 0 deletions src/libostree/ostree-repo.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to figure out a good way to have reliable documentation for these keys. It's a downside of the gvariant approach.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. You can do decently in the FORMAT documentation, but it could definitely be better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also just turn this into a few boxed structs if that would be better. I actually really prefer working with actual data structures rather than variants, but I chose a variant to allow more flexibility for future changes.

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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps disabled at build time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I'm pretty sure I copied this from another optional GPG feature, but there's no reason to copy the slightly wrong grammar.

#endif /* OSTREE_DISABLE_GPGME */
}

/**
* ostree_repo_remote_fetch_summary:
* @self: Self
Expand Down
40 changes: 40 additions & 0 deletions src/libostree/ostree-repo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like this is really a mx perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly. This is copying the gpgme approach where 0 is no expiration (as noted a little later in the comment). I could special case 0 to allow for a mx. According to the OpenPGP RFC either missing or 0 are interpreted as not expiring. I think treating 0 as never expires is a little simpler and consistent with gpgme.

* 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,
Expand Down