diff options
-rw-r--r-- | Makefile-libostree.am | 1 | ||||
-rw-r--r-- | src/libostree/ostree-repo-pull-private.h | 28 | ||||
-rw-r--r-- | src/libostree/ostree-repo-pull-verify.c | 302 | ||||
-rw-r--r-- | src/libostree/ostree-repo-pull.c | 282 |
4 files changed, 338 insertions, 275 deletions
diff --git a/Makefile-libostree.am b/Makefile-libostree.am index c0a7ac9f..96b9249b 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -95,6 +95,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-repo-commit.c \ src/libostree/ostree-repo-pull.c \ src/libostree/ostree-repo-pull-private.h \ + src/libostree/ostree-repo-pull-verify.c \ src/libostree/ostree-repo-libarchive.c \ src/libostree/ostree-repo-prune.c \ src/libostree/ostree-repo-refs.c \ diff --git a/src/libostree/ostree-repo-pull-private.h b/src/libostree/ostree-repo-pull-private.h index 0ed0fdff..b9eb2342 100644 --- a/src/libostree/ostree-repo-pull-private.h +++ b/src/libostree/ostree-repo-pull-private.h @@ -137,4 +137,32 @@ typedef struct { GSource *idle_src; } OtPullData; +gboolean +_sign_verify_for_remote (OstreeRepo *repo, + const gchar *remote_name, + GBytes *signed_data, + GVariant *metadata, + GError **error); + +gboolean +_signapi_load_public_keys (OstreeSign *sign, + OstreeRepo *repo, + const gchar *remote_name, + GError **error); + +gboolean +_verify_unwritten_commit (OtPullData *pull_data, + const char *checksum, + GVariant *commit, + GVariant *detached_metadata, + const OstreeCollectionRef *ref, + GCancellable *cancellable, + GError **error); + +gboolean +_process_gpg_verify_result (OtPullData *pull_data, + const char *checksum, + OstreeGpgVerifyResult *result, + GError **error); + G_END_DECLS diff --git a/src/libostree/ostree-repo-pull-verify.c b/src/libostree/ostree-repo-pull-verify.c new file mode 100644 index 00000000..c1eab6c3 --- /dev/null +++ b/src/libostree/ostree-repo-pull-verify.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2020 Red Hat, Inc. + * Copyright © 2017 Endless Mobile, 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 "libglnx.h" +#include "ostree.h" +#include "otutil.h" +#include "ostree-repo-pull-private.h" +#include "ostree-repo-private.h" + +#include "ostree-core-private.h" +#include "ostree-repo-static-delta-private.h" +#include "ostree-metalink.h" +#include "ostree-fetcher-util.h" +#include "ostree-remote-private.h" +#include "ot-fs-utils.h" + +#include <gio/gunixinputstream.h> +#include <sys/statvfs.h> +#ifdef HAVE_LIBSYSTEMD +#include <systemd/sd-journal.h> +#endif + +#include "ostree-sign.h" + +static gboolean +get_signapi_remote_option (OstreeRepo *repo, + OstreeSign *sign, + const char *remote_name, + const char *keysuffix, + char **out_value, + GError **error) +{ + g_autofree char *key = g_strdup_printf ("verification-%s-%s", ostree_sign_get_name (sign), keysuffix); + return ostree_repo_get_remote_option (repo, remote_name, key, NULL, out_value, error); +} + +/* _signapi_load_public_keys: + * + * Load public keys according remote's configuration: + * inlined key passed via config option `verification-<signapi>-key` or + * file name with public keys via `verification-<signapi>-file` option. + * + * If both options are set then load all all public keys + * both from file and inlined in config. + * + * Returns: %FALSE if any source is configured but nothing has been loaded. + * Returns: %TRUE if no configuration or any key loaded. + * */ +gboolean +_signapi_load_public_keys (OstreeSign *sign, + OstreeRepo *repo, + const gchar *remote_name, + GError **error) +{ + g_autofree gchar *pk_ascii = NULL; + g_autofree gchar *pk_file = NULL; + gboolean loaded_from_file = TRUE; + gboolean loaded_inlined = TRUE; + + if (!get_signapi_remote_option (repo, sign, remote_name, "file", &pk_file, error)) + return FALSE; + if (!get_signapi_remote_option (repo, sign, remote_name, "key", &pk_ascii, error)) + return FALSE; + + /* return TRUE if there is no configuration for remote */ + if ((pk_file == NULL) &&(pk_ascii == NULL)) + { + /* It is expected what remote may have verification file as + * a part of configuration. Hence there is not a lot of sense + * for automatic resolve of per-remote keystore file as it + * used in find_keyring () for GPG. + * If it is needed to add the similar mechanism, it is preferable + * to pass the path to ostree_sign_load_pk () via GVariant options + * and call it here for loading with method and file structure + * specific for signature type. + */ + return TRUE; + } + + if (pk_file != NULL) + { + g_autoptr (GError) local_error = NULL; + g_autoptr (GVariantBuilder) builder = NULL; + g_autoptr (GVariant) options = NULL; + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (pk_file)); + options = g_variant_builder_end (builder); + + if (ostree_sign_load_pk (sign, options, &local_error)) + loaded_from_file = TRUE; + else + { + return glnx_throw (error, "Failed loading '%s' keys from '%s", + ostree_sign_get_name (sign), pk_file); + } + } + + if (pk_ascii != NULL) + { + g_autoptr (GError) local_error = NULL; + g_autoptr (GVariant) pk = g_variant_new_string(pk_ascii); + + /* Add inlined public key */ + if (loaded_from_file) + loaded_inlined = ostree_sign_add_pk (sign, pk, &local_error); + else + loaded_inlined = ostree_sign_set_pk (sign, pk, &local_error); + + if (!loaded_inlined) + { + return glnx_throw (error, "Failed loading '%s' keys from inline `verification-key`", + ostree_sign_get_name (sign)); + } + } + + /* Return true if able to load from any source */ + if (!(loaded_from_file || loaded_inlined)) + return glnx_throw (error, "No keys found"); + + return TRUE; +} + +gboolean +_sign_verify_for_remote (OstreeRepo *repo, + const gchar *remote_name, + GBytes *signed_data, + GVariant *metadata, + GError **error) +{ + /* list all signature types in detached metadata and check if signed by any? */ + g_auto (GStrv) names = ostree_sign_list_names(); + guint n_invalid_signatures = 0; + guint n_unknown_signatures = 0; + g_autoptr (GError) last_sig_error = NULL; + gboolean found_sig = FALSE; + + for (char **iter=names; iter && *iter; iter++) + { + g_autoptr (OstreeSign) sign = NULL; + g_autoptr (GVariant) signatures = NULL; + const gchar *signature_key = NULL; + GVariantType *signature_format = NULL; + + if ((sign = ostree_sign_get_by_name (*iter, NULL)) == NULL) + { + n_unknown_signatures++; + continue; + } + + signature_key = ostree_sign_metadata_key (sign); + signature_format = (GVariantType *) ostree_sign_metadata_format (sign); + + signatures = g_variant_lookup_value (metadata, + signature_key, + signature_format); + + /* If not found signatures for requested signature subsystem */ + if (!signatures) + continue; + + /* Try to load public key(s) according remote's configuration */ + if (!_signapi_load_public_keys (sign, repo, remote_name, error)) + return FALSE; + + found_sig = TRUE; + + /* Return true if any signature fit to pre-loaded public keys. + * If no keys configured -- then system configuration will be used */ + if (!ostree_sign_data_verify (sign, + signed_data, + signatures, + last_sig_error ? NULL : &last_sig_error)) + { + n_invalid_signatures++; + continue; + } + /* Accept the first valid signature */ + return TRUE; + } + + if (!found_sig) + { + if (n_unknown_signatures > 0) + return glnx_throw (error, "No signatures found (%d unknown type)", n_unknown_signatures); + return glnx_throw (error, "No signatures found"); + } + + g_assert (last_sig_error); + g_propagate_error (error, g_steal_pointer (&last_sig_error)); + if (n_invalid_signatures > 1) + glnx_prefix_error (error, "(%d other invalid signatures)", n_invalid_signatures-1); + return FALSE; +} + + +#ifndef OSTREE_DISABLE_GPGME +gboolean +_process_gpg_verify_result (OtPullData *pull_data, + const char *checksum, + OstreeGpgVerifyResult *result, + GError **error) +{ + const char *error_prefix = glnx_strjoina ("Commit ", checksum); + GLNX_AUTO_PREFIX_ERROR(error_prefix, error); + if (result == NULL) + return FALSE; + + /* Allow callers to output the results immediately. */ + g_signal_emit_by_name (pull_data->repo, + "gpg-verify-result", + checksum, result); + + if (!ostree_gpg_verify_result_require_valid_signature (result, error)) + return FALSE; + + + /* We now check both *before* writing the commit, and after. Because the + * behavior used to be only verifiying after writing, we need to handle + * the case of "written but not verified". But we also don't want to check + * twice, as that'd result in duplicate signals. + */ + g_hash_table_add (pull_data->verified_commits, g_strdup (checksum)); + + return TRUE; +} +#endif /* OSTREE_DISABLE_GPGME */ + +gboolean +_verify_unwritten_commit (OtPullData *pull_data, + const char *checksum, + GVariant *commit, + GVariant *detached_metadata, + const OstreeCollectionRef *ref, + GCancellable *cancellable, + GError **error) +{ + + if (pull_data->gpg_verify || pull_data->sign_verify) + /* Shouldn't happen, but see comment in process_gpg_verify_result() */ + if (g_hash_table_contains (pull_data->verified_commits, checksum)) + return TRUE; + + g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit); + +#ifndef OSTREE_DISABLE_GPGME + if (pull_data->gpg_verify) + { + const char *keyring_remote = NULL; + + if (ref != NULL) + keyring_remote = g_hash_table_lookup (pull_data->ref_keyring_map, ref); + if (keyring_remote == NULL) + keyring_remote = pull_data->remote_name; + + g_autoptr(OstreeGpgVerifyResult) result = + _ostree_repo_gpg_verify_with_metadata (pull_data->repo, signed_data, + detached_metadata, + keyring_remote, + NULL, NULL, cancellable, error); + if (!_process_gpg_verify_result (pull_data, checksum, result, error)) + return FALSE; + } +#endif /* OSTREE_DISABLE_GPGME */ + + if (pull_data->sign_verify) + { + /* Nothing to check if detached metadata is absent */ + if (detached_metadata == NULL) + return glnx_throw (error, "Can't verify commit without detached metadata"); + + if (!_sign_verify_for_remote (pull_data->repo, pull_data->remote_name, signed_data, detached_metadata, error)) + return glnx_prefix_error (error, "Can't verify commit"); + + /* Mark the commit as verified to avoid double verification + * see process_verify_result () for rationale */ + g_hash_table_add (pull_data->verified_commits, g_strdup (checksum)); + } + + return TRUE; +} diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 6b8c9e66..817307e9 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -36,7 +36,6 @@ #include "ostree-core-private.h" #include "ostree-repo-static-delta-private.h" #include "ostree-metalink.h" -#include "ot-fs-utils.h" #include "ostree-repo-finder.h" #include "ostree-repo-finder-config.h" @@ -51,8 +50,6 @@ #include <systemd/sd-journal.h> #endif -#include "ostree-sign.h" - #define OSTREE_MESSAGE_FETCH_COMPLETE_ID SD_ID128_MAKE(75,ba,3d,eb,0a,f0,41,a9,a4,62,72,ff,85,d9,e7,3e) #define OSTREE_REPO_PULL_CONTENT_PRIORITY (OSTREE_FETCHER_DEFAULT_PRIORITY) @@ -151,14 +148,6 @@ static gboolean scan_one_metadata_object (OtPullData *pull_data, GCancellable *cancellable, GError **error); static void scan_object_queue_data_free (ScanObjectQueueData *scan_data); -static gboolean -ostree_verify_unwritten_commit (OtPullData *pull_data, - const char *checksum, - GVariant *commit, - GVariant *detached_metadata, - const OstreeCollectionRef *ref, - GCancellable *cancellable, - GError **error); static gboolean update_progress (gpointer user_data) @@ -1200,8 +1189,8 @@ meta_fetch_on_complete (GObject *object, * metadata into this hash. */ GVariant *detached_data = g_hash_table_lookup (pull_data->fetched_detached_metadata, checksum); - if (!ostree_verify_unwritten_commit (pull_data, checksum, metadata, detached_data, - fetch_data->requested_ref, pull_data->cancellable, error)) + if (!_verify_unwritten_commit (pull_data, checksum, metadata, detached_data, + fetch_data->requested_ref, pull_data->cancellable, error)) goto out; if (!ostree_repo_mark_commit_partial (pull_data->repo, checksum, TRUE, error)) @@ -1325,263 +1314,6 @@ static_deltapart_fetch_on_complete (GObject *object, g_clear_pointer (&fetch_data, fetch_static_delta_data_free); } -#ifndef OSTREE_DISABLE_GPGME -static gboolean -process_gpg_verify_result (OtPullData *pull_data, - const char *checksum, - OstreeGpgVerifyResult *result, - GError **error) -{ - const char *error_prefix = glnx_strjoina ("Commit ", checksum); - GLNX_AUTO_PREFIX_ERROR(error_prefix, error); - if (result == NULL) - return FALSE; - - /* Allow callers to output the results immediately. */ - g_signal_emit_by_name (pull_data->repo, - "gpg-verify-result", - checksum, result); - - if (!ostree_gpg_verify_result_require_valid_signature (result, error)) - return FALSE; - - - /* We now check both *before* writing the commit, and after. Because the - * behavior used to be only verifiying after writing, we need to handle - * the case of "written but not verified". But we also don't want to check - * twice, as that'd result in duplicate signals. - */ - g_hash_table_add (pull_data->verified_commits, g_strdup (checksum)); - - return TRUE; -} -#endif /* OSTREE_DISABLE_GPGME */ - -static gboolean -get_signapi_remote_option (OstreeRepo *repo, - OstreeSign *sign, - const char *remote_name, - const char *keysuffix, - char **out_value, - GError **error) -{ - g_autofree char *key = g_strdup_printf ("verification-%s-%s", ostree_sign_get_name (sign), keysuffix); - return ostree_repo_get_remote_option (repo, remote_name, key, NULL, out_value, error); -} - -/* _load_public_keys: - * - * Load public keys according remote's configuration: - * inlined key passed via config option `verification-<signapi>-key` or - * file name with public keys via `verification-<signapi>-file` option. - * - * If both options are set then load all all public keys - * both from file and inlined in config. - * - * Returns: %FALSE if any source is configured but nothing has been loaded. - * Returns: %TRUE if no configuration or any key loaded. - * */ -static gboolean -_load_public_keys (OstreeSign *sign, - OstreeRepo *repo, - const gchar *remote_name, - GError **error) -{ - g_autofree gchar *pk_ascii = NULL; - g_autofree gchar *pk_file = NULL; - gboolean loaded_from_file = TRUE; - gboolean loaded_inlined = TRUE; - - if (!get_signapi_remote_option (repo, sign, remote_name, "file", &pk_file, error)) - return FALSE; - if (!get_signapi_remote_option (repo, sign, remote_name, "key", &pk_ascii, error)) - return FALSE; - - /* return TRUE if there is no configuration for remote */ - if ((pk_file == NULL) &&(pk_ascii == NULL)) - { - /* It is expected what remote may have verification file as - * a part of configuration. Hence there is not a lot of sense - * for automatic resolve of per-remote keystore file as it - * used in find_keyring () for GPG. - * If it is needed to add the similar mechanism, it is preferable - * to pass the path to ostree_sign_load_pk () via GVariant options - * and call it here for loading with method and file structure - * specific for signature type. - */ - return TRUE; - } - - if (pk_file != NULL) - { - g_autoptr (GError) local_error = NULL; - g_autoptr (GVariantBuilder) builder = NULL; - g_autoptr (GVariant) options = NULL; - - builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (pk_file)); - options = g_variant_builder_end (builder); - - if (ostree_sign_load_pk (sign, options, &local_error)) - loaded_from_file = TRUE; - else - { - return glnx_throw (error, "Failed loading '%s' keys from '%s", - ostree_sign_get_name (sign), pk_file); - } - } - - if (pk_ascii != NULL) - { - g_autoptr (GError) local_error = NULL; - g_autoptr (GVariant) pk = g_variant_new_string(pk_ascii); - - /* Add inlined public key */ - if (loaded_from_file) - loaded_inlined = ostree_sign_add_pk (sign, pk, &local_error); - else - loaded_inlined = ostree_sign_set_pk (sign, pk, &local_error); - - if (!loaded_inlined) - { - return glnx_throw (error, "Failed loading '%s' keys from inline `verification-key`", - ostree_sign_get_name (sign)); - } - } - - /* Return true if able to load from any source */ - if (!(loaded_from_file || loaded_inlined)) - return glnx_throw (error, "No keys found"); - - return TRUE; -} - -static gboolean -_sign_verify_for_remote (OstreeRepo *repo, - const gchar *remote_name, - GBytes *signed_data, - GVariant *metadata, - GError **error) -{ - /* list all signature types in detached metadata and check if signed by any? */ - g_auto (GStrv) names = ostree_sign_list_names(); - guint n_invalid_signatures = 0; - guint n_unknown_signatures = 0; - g_autoptr (GError) last_sig_error = NULL; - gboolean found_sig = FALSE; - - for (char **iter=names; iter && *iter; iter++) - { - g_autoptr (OstreeSign) sign = NULL; - g_autoptr (GVariant) signatures = NULL; - const gchar *signature_key = NULL; - GVariantType *signature_format = NULL; - - if ((sign = ostree_sign_get_by_name (*iter, NULL)) == NULL) - { - n_unknown_signatures++; - continue; - } - - signature_key = ostree_sign_metadata_key (sign); - signature_format = (GVariantType *) ostree_sign_metadata_format (sign); - - signatures = g_variant_lookup_value (metadata, - signature_key, - signature_format); - - /* If not found signatures for requested signature subsystem */ - if (!signatures) - continue; - - /* Try to load public key(s) according remote's configuration */ - if (!_load_public_keys (sign, repo, remote_name, error)) - return FALSE; - - found_sig = TRUE; - - /* Return true if any signature fit to pre-loaded public keys. - * If no keys configured -- then system configuration will be used */ - if (!ostree_sign_data_verify (sign, - signed_data, - signatures, - last_sig_error ? NULL : &last_sig_error)) - { - n_invalid_signatures++; - continue; - } - /* Accept the first valid signature */ - return TRUE; - } - - if (!found_sig) - { - if (n_unknown_signatures > 0) - return glnx_throw (error, "No signatures found (%d unknown type)", n_unknown_signatures); - return glnx_throw (error, "No signatures found"); - } - - g_assert (last_sig_error); - g_propagate_error (error, g_steal_pointer (&last_sig_error)); - if (n_invalid_signatures > 1) - glnx_prefix_error (error, "(%d other invalid signatures)", n_invalid_signatures-1); - return FALSE; -} - -static gboolean -ostree_verify_unwritten_commit (OtPullData *pull_data, - const char *checksum, - GVariant *commit, - GVariant *detached_metadata, - const OstreeCollectionRef *ref, - GCancellable *cancellable, - GError **error) -{ - - if (pull_data->gpg_verify || pull_data->sign_verify) - /* Shouldn't happen, but see comment in process_gpg_verify_result() */ - if (g_hash_table_contains (pull_data->verified_commits, checksum)) - return TRUE; - - g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit); - -#ifndef OSTREE_DISABLE_GPGME - if (pull_data->gpg_verify) - { - const char *keyring_remote = NULL; - - if (ref != NULL) - keyring_remote = g_hash_table_lookup (pull_data->ref_keyring_map, ref); - if (keyring_remote == NULL) - keyring_remote = pull_data->remote_name; - - g_autoptr(OstreeGpgVerifyResult) result = - _ostree_repo_gpg_verify_with_metadata (pull_data->repo, signed_data, - detached_metadata, - keyring_remote, - NULL, NULL, cancellable, error); - if (!process_gpg_verify_result (pull_data, checksum, result, error)) - return FALSE; - } -#endif /* OSTREE_DISABLE_GPGME */ - - if (pull_data->sign_verify) - { - /* Nothing to check if detached metadata is absent */ - if (detached_metadata == NULL) - return glnx_throw (error, "Can't verify commit without detached metadata"); - - if (!_sign_verify_for_remote (pull_data->repo, pull_data->remote_name, signed_data, detached_metadata, error)) - return glnx_prefix_error (error, "Can't verify commit"); - - /* Mark the commit as verified to avoid double verification - * see process_verify_result () for rationale */ - g_hash_table_add (pull_data->verified_commits, g_strdup (checksum)); - } - - return TRUE; -} - static gboolean commitstate_is_partial (OtPullData *pull_data, OstreeRepoCommitState commitstate) @@ -1800,7 +1532,7 @@ scan_commit_object (OtPullData *pull_data, keyring_remote, cancellable, error); - if (!process_gpg_verify_result (pull_data, checksum, result, error)) + if (!_process_gpg_verify_result (pull_data, checksum, result, error)) return FALSE; } #endif /* OSTREE_DISABLE_GPGME */ @@ -1822,7 +1554,7 @@ scan_commit_object (OtPullData *pull_data, continue; /* Try to load public key(s) according remote's configuration */ - if (!_load_public_keys (sign, pull_data->repo, pull_data->remote_name, error)) + if (!_signapi_load_public_keys (sign, pull_data->repo, pull_data->remote_name, error)) return FALSE; found_any_signature = TRUE; @@ -2434,8 +2166,8 @@ process_one_static_delta (OtPullData *pull_data, g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_revision, to_revision, "commitmeta"); g_autoptr(GVariant) detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}")); - if (!ostree_verify_unwritten_commit (pull_data, to_revision, to_commit, detached_data, - ref, cancellable, error)) + if (!_verify_unwritten_commit (pull_data, to_revision, to_commit, detached_data, + ref, cancellable, error)) return FALSE; if (detached_data && !ostree_repo_write_commit_detached_metadata (pull_data->repo, @@ -3664,7 +3396,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (pull_data->gpg_verify || pull_data->gpg_verify_summary) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "'%s': GPG feature is disabled in a build time", + "'%s': GPG feature is disabled at build time", __FUNCTION__); goto out; } |