/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */ /* * Copyright (C) 2015 Colin Walters * Copyright (C) 2019 Denis Pynkin (d4s) * * 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. * * Author: Colin Walters */ #include "config.h" #include "ot-main.h" #include "ot-builtins.h" #include "ostree.h" #include "otutil.h" #include "ostree-core-private.h" #include "ostree-sign.h" static gboolean opt_delete; static gboolean opt_verify; static char *opt_sign_name; static char *opt_filename; static char *opt_keysdir; /* ATTENTION: * Please remember to update the bash-completion script (bash/ostree) and * man page (man/ostree-sign.xml) when changing the option list. */ static GOptionEntry options[] = { { "delete", 'd', 0, G_OPTION_ARG_NONE, &opt_delete, "Delete signatures having any of the KEY-IDs", NULL}, { "verify", 0, 0, G_OPTION_ARG_NONE, &opt_verify, "Verify signatures", NULL}, { "sign-type", 's', 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, #if defined(HAVE_LIBSODIUM) { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_filename, "Read key(s) from file", "NAME"}, { "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"}, #endif { NULL } }; static void usage_error (GOptionContext *context, const char *message, GError **error) { g_autofree char *help = g_option_context_get_help (context, TRUE, NULL); g_printerr ("%s", help); g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message); } gboolean ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr (GOptionContext) context = NULL; g_autoptr (OstreeRepo) repo = NULL; g_autoptr (OstreeSign) sign = NULL; g_autofree char *resolved_commit = NULL; g_autofree char *success_message = NULL; const char *commit; char **key_ids; int n_key_ids, ii; gboolean ret = FALSE; context = g_option_context_new ("COMMIT KEY-ID..."); if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) goto out; if (argc < 2) { usage_error (context, "Need a COMMIT to sign or verify", error); goto out; } commit = argv[1]; /* Verification could be done via system files with public keys */ if (!opt_verify && !opt_filename && argc < 3) { usage_error (context, "Need at least one KEY-ID to sign with", error); goto out; } key_ids = argv + 2; n_key_ids = argc - 2; if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error)) goto out; /* Initialize crypto system */ opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; sign = ostree_sign_get_by_name (opt_sign_name, error); if (sign == NULL) goto out; for (ii = 0; ii < n_key_ids; ii++) { g_autoptr (GVariant) sk = NULL; g_autoptr (GVariant) pk = NULL; if (opt_verify) { g_autoptr (GError) local_error = NULL; // Pass the key as a string pk = g_variant_new_string(key_ids[ii]); if (!ostree_sign_set_pk (sign, pk, &local_error)) continue; if (ostree_sign_commit_verify (sign, repo, resolved_commit, &success_message, cancellable, &local_error)) { g_assert (success_message); g_print ("%s\n", success_message); ret = TRUE; goto out; } } else { // Pass the key as a string sk = g_variant_new_string(key_ids[ii]); if (!ostree_sign_set_sk (sign, sk, error)) { ret = FALSE; goto out; } ret = ostree_sign_commit (sign, repo, resolved_commit, cancellable, error); if (ret != TRUE) goto out; } } /* Try to verify with user-provided file or system configuration */ if (opt_verify) { if ((n_key_ids == 0) || opt_filename) { g_autoptr (GVariantBuilder) builder = NULL; g_autoptr (GVariant) sign_options = NULL; builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); /* Use custom directory with public and revoked keys instead of system-wide directories */ if (opt_keysdir) g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir)); /* The last chance for verification source -- system files */ if (opt_filename) g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_filename)); sign_options = g_variant_builder_end (builder); if (!ostree_sign_load_pk (sign, sign_options, error)) goto out; if (ostree_sign_commit_verify (sign, repo, resolved_commit, &success_message, cancellable, error)) { g_print ("%s\n", success_message); ret = TRUE; } } /* Check via file */ } else { /* Sign with keys from provided file */ if (opt_filename) { g_autoptr (GFile) keyfile = NULL; g_autoptr (GFileInputStream) key_stream_in = NULL; g_autoptr (GDataInputStream) key_data_in = NULL; if (!g_file_test (opt_filename, G_FILE_TEST_IS_REGULAR)) { g_warning ("Can't open file '%s' with keys", opt_filename); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "File object '%s' is not a regular file", opt_filename); goto out; } keyfile = g_file_new_for_path (opt_filename); key_stream_in = g_file_read (keyfile, NULL, error); if (key_stream_in == NULL) goto out; key_data_in = g_data_input_stream_new (G_INPUT_STREAM(key_stream_in)); g_assert (key_data_in != NULL); /* Use simple file format with just a list of base64 public keys per line */ while (TRUE) { gsize len = 0; g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error); g_autoptr (GVariant) sk = NULL; if (*error != NULL) goto out; if (line == NULL) break; // Pass the key as a string sk = g_variant_new_string(line); if (!ostree_sign_set_sk (sign, sk, error)) { ret = FALSE; goto out; } ret = ostree_sign_commit (sign, repo, resolved_commit, cancellable, error); if (ret != TRUE) goto out; } } } // No valid signature found if (opt_verify && (ret != TRUE) && (*error == NULL)) g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "No valid signatures found"); out: /* It is possible to have an error due multiple signatures check */ if (ret == TRUE) g_clear_error (error); return ret; }