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

WIP: GPG remote trusted key updating #2260

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions Makefile-ostree.am
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ ostree_SOURCES += \
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

Expand Down
2 changes: 2 additions & 0 deletions Makefile-otutil.am
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Makefile-tests.am
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions apidoc/ostree-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ 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_update_gpg_keys
ostree_repo_remote_fetch_summary
ostree_repo_remote_fetch_summary_with_options
ostree_repo_reload_config
Expand Down
36 changes: 36 additions & 0 deletions bash/ostree
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1346,9 +1380,11 @@ _ostree_remote() {
gpg-import
list
list-cookies
list-gpg-keys
refs
show-url
summary
update-gpg-keys
"

__ostree_subcommands "$subcommands" && return 0
Expand Down
18 changes: 17 additions & 1 deletion man/ostree-remote.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ Boston, MA 02111-1307, USA.
<cmdsynopsis>
<command>ostree remote gpg-import</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">NAME</arg> <arg choice="opt" rep="repeat">KEY-ID</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ostree remote list-gpg-keys</command> <arg choice="req">NAME</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ostree remote update-gpg-keys</command> <arg choice="req">NAME</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>ostree remote refs</command> <arg choice="req">NAME</arg>
</cmdsynopsis>
Expand Down Expand Up @@ -106,11 +112,21 @@ Boston, MA 02111-1307, USA.
for more information.
</para>
<para>
The <command>gpg-import</command> 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 <command>gpg-import</command> 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
<command>list-gpg-keys</command> subcommand can be used to see the
GPG keys currently associated with a remote repository.
</para>
<para>
The GPG keys to import may be in binary OpenPGP format or ASCII armored. The optional <arg>KEY-ID</arg> 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 <option>--keyring</option> nor <option>--stdin</option> options are given, then keys are imported from the user's personal GPG keyring.
</para>
<para>
The <command>update-gpg-keys</command> 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 <command>list-gpg-keys</command> subcommand.
</para>
<para>
The various cookie related command allow management of a remote specific cookie jar.
</para>
Expand Down
3 changes: 3 additions & 0 deletions src/libostree/libostree-devel.sym
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

/* Add new symbols here. Release commits should copy this section into -released.sym. */
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
Expand Down
194 changes: 147 additions & 47 deletions src/libostree/ostree-gpg-verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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++)
Expand All @@ -162,47 +135,174 @@ _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");
return FALSE;
}
}
}

gpgme_set_armor (gpgme_ctx, armor);

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;
}
}

gpgme_set_armor (result->context, armor);
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,
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
Expand Down
Loading