Blame src/ostree/ot-builtin-gpg-sign.c

rpm-build 0fba15
/*
rpm-build 0fba15
 * Copyright (C) 2015 Colin Walters <walters@verbum.org>
rpm-build 0fba15
 *
rpm-build 0fba15
 * SPDX-License-Identifier: LGPL-2.0+
rpm-build 0fba15
 *
rpm-build 0fba15
 * This library is free software; you can redistribute it and/or
rpm-build 0fba15
 * modify it under the terms of the GNU Lesser General Public
rpm-build 0fba15
 * License as published by the Free Software Foundation; either
rpm-build 0fba15
 * version 2 of the License, or (at your option) any later version.
rpm-build 0fba15
 *
rpm-build 0fba15
 * This library is distributed in the hope that it will be useful,
rpm-build 0fba15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
rpm-build 0fba15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
rpm-build 0fba15
 * Lesser General Public License for more details.
rpm-build 0fba15
 *
rpm-build 0fba15
 * You should have received a copy of the GNU Lesser General Public
rpm-build 0fba15
 * License along with this library; if not, write to the
rpm-build 0fba15
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
rpm-build 0fba15
 * Boston, MA 02111-1307, USA.
rpm-build 0fba15
 *
rpm-build 0fba15
 * Author: Colin Walters <walters@verbum.org>
rpm-build 0fba15
 */
rpm-build 0fba15
rpm-build 0fba15
#include "config.h"
rpm-build 0fba15
rpm-build 0fba15
#include "ot-main.h"
rpm-build 0fba15
#include "ot-builtins.h"
rpm-build 0fba15
#include "ostree.h"
rpm-build 0fba15
#include "otutil.h"
rpm-build 0fba15
#include "ostree-core-private.h"
rpm-build 0fba15
rpm-build 0fba15
static gboolean opt_delete;
rpm-build 0fba15
static char *opt_gpg_homedir;
rpm-build 0fba15
rpm-build 0fba15
/* ATTENTION:
rpm-build 0fba15
 * Please remember to update the bash-completion script (bash/ostree) and
rpm-build 0fba15
 * man page (man/ostree-gpg-sign.xml) when changing the option list.
rpm-build 0fba15
 */
rpm-build 0fba15
rpm-build 0fba15
static GOptionEntry options[] = {
rpm-build 0fba15
  { "delete", 'd', 0, G_OPTION_ARG_NONE, &opt_delete, "Delete signatures having any of the GPG KEY-IDs" },
rpm-build 0fba15
  { "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR" },
rpm-build 0fba15
  { NULL }
rpm-build 0fba15
};
rpm-build 0fba15
rpm-build 0fba15
static void
rpm-build 0fba15
usage_error (GOptionContext *context, const char *message, GError **error)
rpm-build 0fba15
{
rpm-build 0fba15
  g_autofree char *help = g_option_context_get_help (context, TRUE, NULL);
rpm-build 0fba15
  g_printerr ("%s", help);
rpm-build 0fba15
  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message);
rpm-build 0fba15
}
rpm-build 0fba15
rpm-build 0fba15
static gboolean
rpm-build 0fba15
delete_signatures (OstreeRepo *repo,
rpm-build 0fba15
                   const char *commit_checksum,
rpm-build 0fba15
                   const char * const *key_ids,
rpm-build 0fba15
                   guint n_key_ids,
rpm-build 0fba15
                   guint *out_n_deleted,
rpm-build 0fba15
                   GCancellable *cancellable,
rpm-build 0fba15
                   GError **error)
rpm-build 0fba15
{
rpm-build 0fba15
  GVariantDict metadata_dict;
rpm-build 0fba15
  g_autoptr(OstreeGpgVerifyResult) result = NULL;
rpm-build 0fba15
  g_autoptr(GVariant) old_metadata = NULL;
rpm-build 0fba15
  g_autoptr(GVariant) new_metadata = NULL;
rpm-build 0fba15
  g_autoptr(GVariant) signature_data = NULL;
rpm-build 0fba15
  GVariantIter iter;
rpm-build 0fba15
  GVariant *child;
rpm-build 0fba15
  GQueue signatures = G_QUEUE_INIT;
rpm-build 0fba15
  GQueue trash = G_QUEUE_INIT;
rpm-build 0fba15
  guint n_deleted = 0;
rpm-build 0fba15
  guint ii;
rpm-build 0fba15
  gboolean ret = FALSE;
rpm-build 0fba15
  GError *local_error = NULL;
rpm-build 0fba15
rpm-build 0fba15
  /* XXX Should this code be a new OstreeRepo function in libostree?
rpm-build 0fba15
   *     Feels slightly too low-level here, and I have to know about
rpm-build 0fba15
   *     the metadata key name and format which are both declared in
rpm-build 0fba15
   *     ostree-core-private.h.
rpm-build 0fba15
   *
rpm-build 0fba15
   *     OTOH, would this really be a useful addition to libostree?
rpm-build 0fba15
   */
rpm-build 0fba15
rpm-build 0fba15
  if (!ostree_repo_read_commit_detached_metadata (repo,
rpm-build 0fba15
                                                  commit_checksum,
rpm-build 0fba15
                                                  &old_metadata,
rpm-build 0fba15
                                                  cancellable,
rpm-build 0fba15
                                                  error))
rpm-build 0fba15
    goto out;
rpm-build 0fba15
rpm-build 0fba15
  g_variant_dict_init (&metadata_dict, old_metadata);
rpm-build 0fba15
rpm-build 0fba15
  signature_data = g_variant_dict_lookup_value (&metadata_dict,
rpm-build 0fba15
                                                _OSTREE_METADATA_GPGSIGS_NAME,
rpm-build 0fba15
                                                G_VARIANT_TYPE ("aay"));
rpm-build 0fba15
rpm-build 0fba15
  /* Taking the approach of deleting whatever matches we find for the
rpm-build 0fba15
   * provided key IDs, even if we don't find a match for EVERY key ID.
rpm-build 0fba15
   * So no signatures means no matches, which is okay... I guess. */
rpm-build 0fba15
  if (signature_data == NULL)
rpm-build 0fba15
    {
rpm-build 0fba15
      g_variant_dict_clear (&metadata_dict);
rpm-build 0fba15
      goto shortcut;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  /* Parse the signatures on this commit by running a verify operation
rpm-build 0fba15
   * on it.  Use the result to match key IDs to signatures for deletion.
rpm-build 0fba15
   *
rpm-build 0fba15
   * XXX Reading detached metadata from disk twice here.  Another reason
rpm-build 0fba15
   *     to move this into libostree?
rpm-build 0fba15
   */
rpm-build 0fba15
  result = ostree_repo_verify_commit_ext (repo, commit_checksum,
rpm-build 0fba15
                                          NULL, NULL,
rpm-build 0fba15
                                          cancellable, &local_error);
rpm-build 0fba15
  if (result == NULL)
rpm-build 0fba15
    {
rpm-build 0fba15
      g_variant_dict_clear (&metadata_dict);
rpm-build 0fba15
      goto out;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  /* Convert the GVariant array to a GQueue. */
rpm-build 0fba15
  g_variant_iter_init (&iter, signature_data);
rpm-build 0fba15
  while ((child = g_variant_iter_next_value (&iter)) != NULL)
rpm-build 0fba15
    {
rpm-build 0fba15
      /* Takes ownership of the child. */
rpm-build 0fba15
      g_queue_push_tail (&signatures, child);
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  /* Signature count and ordering of signatures in the GQueue and
rpm-build 0fba15
   * OstreeGpgVerifyResult must agree.  We use this below to mark
rpm-build 0fba15
   * items in the GQueue for deletion based on the index returned
rpm-build 0fba15
   * by ostree_gpg_verify_result_lookup(). */
rpm-build 0fba15
  g_assert_cmpuint (ostree_gpg_verify_result_count_all (result), ==, signatures.length);
rpm-build 0fba15
rpm-build 0fba15
  /* Build a trash queue which points at nodes in the signature queue. */
rpm-build 0fba15
  for (ii = 0; ii < n_key_ids; ii++)
rpm-build 0fba15
    {
rpm-build 0fba15
      guint index;
rpm-build 0fba15
rpm-build 0fba15
      if (ostree_gpg_verify_result_lookup (result, key_ids[ii], &index))
rpm-build 0fba15
        {
rpm-build 0fba15
          GList *link = g_queue_peek_nth_link (&signatures, index);
rpm-build 0fba15
rpm-build 0fba15
          /* Avoid duplicates in the trash queue. */
rpm-build 0fba15
          if (g_queue_find (&trash, link) == NULL)
rpm-build 0fba15
            g_queue_push_tail (&trash, link);
rpm-build 0fba15
        }
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  n_deleted = trash.length;
rpm-build 0fba15
rpm-build 0fba15
  /* Reduce the signature queue by emptying the trash. */
rpm-build 0fba15
  while (!g_queue_is_empty (&trash))
rpm-build 0fba15
    {
rpm-build 0fba15
      GList *link = g_queue_pop_head (&trash);
rpm-build 0fba15
      g_variant_unref (link->data);
rpm-build 0fba15
      g_queue_delete_link (&signatures, link);
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  /* Update the metadata dictionary. */
rpm-build 0fba15
  if (g_queue_is_empty (&signatures))
rpm-build 0fba15
    {
rpm-build 0fba15
      g_variant_dict_remove (&metadata_dict, _OSTREE_METADATA_GPGSIGS_NAME);
rpm-build 0fba15
    }
rpm-build 0fba15
  else
rpm-build 0fba15
    {
rpm-build 0fba15
      GVariantBuilder signature_builder;
rpm-build 0fba15
rpm-build 0fba15
      g_variant_builder_init (&signature_builder, G_VARIANT_TYPE ("aay"));
rpm-build 0fba15
rpm-build 0fba15
      while (!g_queue_is_empty (&signatures))
rpm-build 0fba15
        {
rpm-build 0fba15
          GVariant *child = g_queue_pop_head (&signatures);
rpm-build 0fba15
          g_variant_builder_add_value (&signature_builder, child);
rpm-build 0fba15
          g_variant_unref (child);
rpm-build 0fba15
        }
rpm-build 0fba15
rpm-build 0fba15
      g_variant_dict_insert_value (&metadata_dict,
rpm-build 0fba15
                                   _OSTREE_METADATA_GPGSIGS_NAME,
rpm-build 0fba15
                                   g_variant_builder_end (&signature_builder));
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  /* Commit the new metadata. */
rpm-build 0fba15
  new_metadata = g_variant_dict_end (&metadata_dict);
rpm-build 0fba15
  if (!ostree_repo_write_commit_detached_metadata (repo,
rpm-build 0fba15
                                                   commit_checksum,
rpm-build 0fba15
                                                   new_metadata,
rpm-build 0fba15
                                                   cancellable,
rpm-build 0fba15
                                                   error))
rpm-build 0fba15
    goto out;
rpm-build 0fba15
rpm-build 0fba15
shortcut:
rpm-build 0fba15
rpm-build 0fba15
  if (out_n_deleted != NULL)
rpm-build 0fba15
    *out_n_deleted = n_deleted;
rpm-build 0fba15
rpm-build 0fba15
  ret = TRUE;
rpm-build 0fba15
rpm-build 0fba15
out:
rpm-build 0fba15
  return ret;
rpm-build 0fba15
}
rpm-build 0fba15
rpm-build 0fba15
gboolean
rpm-build 0fba15
ostree_builtin_gpg_sign (int argc, char **argv,OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
rpm-build 0fba15
{
rpm-build 0fba15
  g_autoptr(GOptionContext) context = NULL;
rpm-build 0fba15
  g_autoptr(OstreeRepo) repo = NULL;
rpm-build 0fba15
  g_autofree char *resolved_commit = NULL;
rpm-build 0fba15
  const char *commit;
rpm-build 0fba15
  char **key_ids;
rpm-build 0fba15
  int n_key_ids, ii;
rpm-build 0fba15
  gboolean ret = FALSE;
rpm-build 0fba15
rpm-build 0fba15
  context = g_option_context_new ("COMMIT KEY-ID...");
rpm-build 0fba15
rpm-build 0fba15
  if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
rpm-build 0fba15
    goto out;
rpm-build 0fba15
rpm-build 0fba15
  if (argc < 2)
rpm-build 0fba15
    {
rpm-build 0fba15
      usage_error (context, "Need a COMMIT to sign", error);
rpm-build 0fba15
      goto out;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  if (argc < 3)
rpm-build 0fba15
    {
rpm-build 0fba15
      usage_error (context, "Need at least one GPG KEY-ID to sign with", error);
rpm-build 0fba15
      goto out;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  commit = argv[1];
rpm-build 0fba15
  key_ids = argv + 2;
rpm-build 0fba15
  n_key_ids = argc - 2;
rpm-build 0fba15
rpm-build 0fba15
  if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error))
rpm-build 0fba15
    goto out;
rpm-build 0fba15
rpm-build 0fba15
  if (opt_delete)
rpm-build 0fba15
    {
rpm-build 0fba15
      guint n_deleted = 0;
rpm-build 0fba15
rpm-build 0fba15
      if (delete_signatures (repo, resolved_commit,
rpm-build 0fba15
                             (const char * const *) key_ids, n_key_ids,
rpm-build 0fba15
                             &n_deleted, cancellable, error))
rpm-build 0fba15
        {
rpm-build 0fba15
          g_print ("Signatures deleted: %u\n", n_deleted);
rpm-build 0fba15
          ret = TRUE;
rpm-build 0fba15
        }
rpm-build 0fba15
rpm-build 0fba15
      goto out;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  for (ii = 0; ii < n_key_ids; ii++)
rpm-build 0fba15
    {
rpm-build 0fba15
      if (!ostree_repo_sign_commit (repo, resolved_commit, key_ids[ii],
rpm-build 0fba15
                                    opt_gpg_homedir, cancellable, error))
rpm-build 0fba15
        goto out;
rpm-build 0fba15
    }
rpm-build 0fba15
rpm-build 0fba15
  ret = TRUE;
rpm-build 0fba15
rpm-build 0fba15
out:
rpm-build 0fba15
  return ret;
rpm-build 0fba15
}