From 46667567c5a1f17ecb2efbf8fd21e7711539003e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 8 Jul 2020 12:15:15 +0200 Subject: [PATCH 01/12] lib/deltas: Add inline signature for static-delta superblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While the commits contained in the single static-delta file are signed so we can check them and operate on trusted data, the superblock isn't signed in any way, so it end up operating on untrusted data to: 1. actually find where the trusted data is, and 2. check whether the update is fit for the current device by looking at the collection id stored in the metadata This commit generates signatures of all static data, and concatenate them to the existing static delta format, i.e. as a GVariant layout `a{sv}ay` where - a{sv}: signatures - ay: existing delta variant Signed-off-by: Frédéric Danis --- .../ostree-repo-static-delta-compilation.c | 97 +++++++++++++++++-- .../ostree-repo-static-delta-private.h | 19 ++++ 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 3e9a8e30..753f8aee 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -36,6 +36,8 @@ #include "libglnx.h" #include "ostree-varint.h" #include "bsdiff/bsdiff.h" +#include "ostree-autocleanups.h" +#include "ostree-sign.h" #define CONTENT_SIZE_SIMILARITY_THRESHOLD_PERCENT (30) @@ -1335,6 +1337,8 @@ get_fallback_headers (OstreeRepo *self, * - verbose: b: Print diagnostic messages. Default FALSE. * - endianness: b: Deltas use host byte order by default; this option allows choosing (G_BIG_ENDIAN or G_LITTLE_ENDIAN) * - filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository. + * - sign-name: ay: Signature type to use. + * - sign-key-ids: as: Array of keys used to sign delta superblock. */ gboolean ostree_repo_static_delta_generate (OstreeRepo *self, @@ -1368,6 +1372,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self, g_autoptr(GPtrArray) builder_fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); g_auto(GLnxTmpfile) descriptor_tmpf = { 0, }; g_autoptr(OtVariantBuilder) descriptor_builder = NULL; + const char *opt_sign_name; + const char **opt_key_ids; if (!g_variant_lookup (params, "min-fallback-size", "u", &min_fallback_size)) min_fallback_size = 4; @@ -1407,6 +1413,12 @@ ostree_repo_static_delta_generate (OstreeRepo *self, if (!g_variant_lookup (params, "filename", "^&ay", &opt_filename)) opt_filename = NULL; + if (!g_variant_lookup (params, "sign-name", "^&ay", &opt_sign_name)) + opt_sign_name = NULL; + + if (!g_variant_lookup (params, "sign-key-ids", "^a&s", &opt_key_ids)) + opt_key_ids = NULL; + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, to, &to_commit, error)) return FALSE; @@ -1442,7 +1454,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, cancellable, error)) return FALSE; - if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC, + if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_RDWR | O_CLOEXEC, &descriptor_tmpf, error)) return FALSE; @@ -1586,12 +1598,85 @@ ostree_repo_static_delta_generate (OstreeRepo *self, g_printerr ("bsdiff=%u objects\n", builder.n_bsdiff); } - if (fchmod (descriptor_tmpf.fd, 0644) < 0) - return glnx_throw_errno_prefix (error, "fchmod"); + if (opt_sign_name != NULL && opt_key_ids != NULL) + { + g_autoptr(GBytes) tmpdata = NULL; + g_autoptr(OstreeSign) sign = NULL; + const gchar *signature_key = NULL; + g_autoptr(GVariantBuilder) signature_builder = NULL; + g_auto(GLnxTmpfile) descriptor_sign_tmpf = { 0, }; + g_autoptr(OtVariantBuilder) descriptor_sign_builder = NULL; - if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE, - descriptor_dfd, descriptor_name, error)) - return FALSE; + lseek (descriptor_tmpf.fd, 0, SEEK_SET); + tmpdata = glnx_fd_readall_bytes (descriptor_tmpf.fd, cancellable, error); + if (!tmpdata) + return FALSE; + + sign = ostree_sign_get_by_name (opt_sign_name, error); + if (sign == NULL) + return FALSE; + + signature_key = ostree_sign_metadata_key (sign); + const gchar *signature_format = ostree_sign_metadata_format (sign); + + signature_builder = g_variant_builder_new (G_VARIANT_TYPE (signature_format)); + + for (const char **iter = opt_key_ids; iter && *iter; iter++) + { + const char *keyid = *iter; + g_autoptr(GVariant) secret_key = NULL; + g_autoptr(GBytes) signature_bytes = NULL; + + secret_key = g_variant_new_string (keyid); + if (!ostree_sign_set_sk (sign, secret_key, error)) + return FALSE; + + if (!ostree_sign_data (sign, tmpdata, &signature_bytes, + NULL, error)) + return FALSE; + + g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes)); + } + + if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC, + &descriptor_sign_tmpf, error)) + return FALSE; + + descriptor_sign_builder = ot_variant_builder_new (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SIGNED_FORMAT), + descriptor_sign_tmpf.fd); + + if (!ot_variant_builder_add (descriptor_sign_builder, error, "t", + GUINT64_TO_BE (OSTREE_STATIC_DELTA_SIGNED_MAGIC))) + return FALSE; + if (!ot_variant_builder_add (descriptor_sign_builder, error, "@ay", ot_gvariant_new_ay_bytes (tmpdata))) + return FALSE; + if (!ot_variant_builder_open (descriptor_sign_builder, G_VARIANT_TYPE ("a{sv}"), error)) + return FALSE; + if (!ot_variant_builder_add (descriptor_sign_builder, error, "{sv}", + signature_key, g_variant_builder_end(signature_builder))) + return FALSE; + if (!ot_variant_builder_close (descriptor_sign_builder, error)) + return FALSE; + + if (!ot_variant_builder_end (descriptor_sign_builder, error)) + return FALSE; + + if (fchmod (descriptor_sign_tmpf.fd, 0644) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + if (!glnx_link_tmpfile_at (&descriptor_sign_tmpf, GLNX_LINK_TMPFILE_REPLACE, + descriptor_dfd, descriptor_name, error)) + return FALSE; + } + else + { + if (fchmod (descriptor_tmpf.fd, 0644) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE, + descriptor_dfd, descriptor_name, error)) + return FALSE; + } return TRUE; } diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h index ff8de9d4..5a2e6879 100644 --- a/src/libostree/ostree-repo-static-delta-private.h +++ b/src/libostree/ostree-repo-static-delta-private.h @@ -104,6 +104,25 @@ G_BEGIN_DECLS */ #define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" +/** + * OSTREE_STATIC_DELTA_SIGNED_FORMAT + * + * magic: t magic number, 8 bytes for alignment + * superblock: ay delta supeblock variant + * signatures: a{sv} + * + * The signed static delta starts with the 'OSTSGNDT' magic number followed by + * the array of bytes containing the superblock used for the signature. + * + * Then, the signatures array contains the signatures of the superblock. A + * signature has the following form: + * type: signature key + * signature: variant depending on type used + */ +#define OSTREE_STATIC_DELTA_SIGNED_FORMAT "(taya{sv})" + +#define OSTREE_STATIC_DELTA_SIGNED_MAGIC 0x4F535453474E4454 /* OSTSGNDT */ + typedef enum { OSTREE_STATIC_DELTA_OPEN_FLAGS_NONE = 0, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM = (1 << 0), From 92efbc00d80f074bf24605ec10fafac9e7eee697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 26 Nov 2019 11:20:10 +0100 Subject: [PATCH 02/12] bin/static-delta: Add support to sign superblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add signing ability to "static-delta generate" builtin. Signed-off-by: Frédéric Danis --- bash/ostree | 5 +++ man/ostree-static-delta.xml | 33 +++++++++++++++ src/ostree/ot-builtin-static-delta.c | 62 ++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/bash/ostree b/bash/ostree index 7256e40a..a7389bd7 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1613,6 +1613,8 @@ _ostree_static_delta_generate() { --repo --set-endianness --to + --sign + --sign-type " local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) @@ -1630,6 +1632,9 @@ _ostree_static_delta_generate() { COMPREPLY=( $( compgen -W "l B" -- "$cur" ) ) return 0 ;; + $options_with_args_glob ) + return 0 + ;; esac case "$cur" in diff --git a/man/ostree-static-delta.xml b/man/ostree-static-delta.xml index dfeef28b..a4bef237 100644 --- a/man/ostree-static-delta.xml +++ b/man/ostree-static-delta.xml @@ -113,6 +113,39 @@ Boston, MA 02111-1307, USA. + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + The default is ed25519. + + + + + ="KEY-ID" + + There KEY-ID is: + + + + + base64-encoded secret key for signing. + + + + + + + ASCII-string used as secret key. + + + + + diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c index 4f9ff2b2..d5e93783 100644 --- a/src/ostree/ot-builtin-static-delta.c +++ b/src/ostree/ot-builtin-static-delta.c @@ -40,6 +40,9 @@ static gboolean opt_swap_endianness; static gboolean opt_inline; static gboolean opt_disable_bsdiff; static gboolean opt_if_not_exists; +static char **opt_key_ids; +static char *opt_sign_name; +static char *opt_keysfilename; #define BUILTINPROTO(name) static gboolean ot_static_delta_builtin_ ## name (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) @@ -88,6 +91,11 @@ static GOptionEntry generate_options[] = { { "max-bsdiff-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_bsdiff_size, "Maximum size in megabytes to consider bsdiff compression for input files", NULL}, { "max-chunk-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_chunk_size, "Maximum size of delta chunks in megabytes", NULL}, { "filename", 0, 0, G_OPTION_ARG_FILENAME, &opt_filename, "Write the delta content to PATH (a directory). If not specified, the OSTree repository is used", "PATH"}, + { "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Sign the delta with", "KEY_ID"}, + { "sign-type", 0, 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_keysfilename, "Read key(s) from file", "NAME"}, +#endif { NULL } }; @@ -326,6 +334,60 @@ ot_static_delta_builtin_generate (int argc, char **argv, OstreeCommandInvocation if (opt_endianness || opt_swap_endianness) g_variant_builder_add (parambuilder, "{sv}", "endianness", g_variant_new_uint32 (endianness)); + if (opt_key_ids || opt_keysfilename) + { + g_autoptr(GPtrArray) key_ids = g_ptr_array_new (); + + for (char **iter = opt_key_ids; iter != NULL && *iter != NULL; ++iter) + g_ptr_array_add (key_ids, *iter); + + if (opt_keysfilename) + { + g_autoptr (GFile) keyfile = NULL; + g_autoptr (GFileInputStream) key_stream_in = NULL; + g_autoptr (GDataInputStream) key_data_in = NULL; + + if (!g_file_test (opt_keysfilename, G_FILE_TEST_IS_REGULAR)) + { + g_warning ("Can't open file '%s' with keys", opt_keysfilename); + return glnx_throw (error, "File object '%s' is not a regular file", opt_keysfilename); + } + + keyfile = g_file_new_for_path (opt_keysfilename); + key_stream_in = g_file_read (keyfile, NULL, error); + if (key_stream_in == NULL) + return FALSE; + + 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) + return FALSE; + + if (line == NULL) + break; + + // Pass the key as a string + g_ptr_array_add (key_ids, g_strdup (line)); + } + } + + g_autoptr(GVariant) key_ids_v = g_variant_new_strv ((const char *const *)key_ids->pdata, + key_ids->len); + g_variant_builder_add (parambuilder, "{s@v}", "sign-key-ids", + g_variant_new_variant (g_steal_pointer (&key_ids_v))); + } + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; + g_variant_builder_add (parambuilder, "{sv}", "sign-name", + g_variant_new_bytestring (opt_sign_name)); + g_print ("Generating static delta:\n"); g_print (" From: %s\n", from_resolved ? from_resolved : "empty"); g_print (" To: %s\n", to_resolved); From 02a19b2c96d9f84d23dcd15b3f20e507f6573a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 28 Nov 2019 12:18:59 +0100 Subject: [PATCH 03/12] lib/deltas: Add signature check API for static-delta superblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This retrieves the signatures and pass the static delta block as an array of bytes to ostree_sign_data_verify(). Signed-off-by: Frédéric Danis --- Makefile-libostree.am | 2 +- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 1 + src/libostree/ostree-repo-static-delta-core.c | 128 ++++++++++++++++++ src/libostree/ostree-repo.h | 8 ++ 5 files changed, 139 insertions(+), 1 deletion(-) diff --git a/Makefile-libostree.am b/Makefile-libostree.am index a180e86b..1d31c4d8 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -183,7 +183,7 @@ endif # USE_GPGME symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym #if BUILDOPT_IS_DEVEL_BUILD -#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym +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= diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 979c8e93..8cafeb1f 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -413,6 +413,7 @@ ostree_repo_list_static_delta_names OstreeStaticDeltaGenerateOpt ostree_repo_static_delta_generate ostree_repo_static_delta_execute_offline +ostree_repo_static_delta_verify_signature ostree_repo_traverse_new_reachable ostree_repo_traverse_new_parents ostree_repo_traverse_parents_get_commits diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 7f1f7e3e..9a847b92 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -22,6 +22,7 @@ global: /* Add symbols here, and uncomment the bits in * Makefile-libostree.am to enable this too. */ + ostree_repo_static_delta_verify_signature; } LIBOSTREE_2020.4; /* Stub section for the stable release *after* this development one; don't diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index 835ec7f3..857473db 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -210,6 +210,61 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, return TRUE; } +static gboolean +_ostree_repo_static_delta_is_signed (OstreeRepo *self, + int fd, + GPtrArray **out_value, + GError **error) +{ + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) delta_sign_magic = NULL; + g_autoptr(GVariant) delta_sign = NULL; + GVariantIter iter; + GVariant *item; + g_autoptr(GPtrArray) signatures = NULL; + gboolean ret = FALSE; + + if (out_value) + *out_value = NULL; + + if (!ot_variant_read_fd (fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta, error)) + return FALSE; + + delta_sign_magic = g_variant_get_child_value (delta, 0); + if (delta_sign_magic == NULL) + return glnx_throw (error, "no signatures in static-delta"); + + if (GUINT64_FROM_BE (g_variant_get_uint64 (delta_sign_magic)) != OSTREE_STATIC_DELTA_SIGNED_MAGIC) + return glnx_throw (error, "no signatures in static-delta"); + + delta_sign = g_variant_get_child_value (delta, 2); + if (delta_sign == NULL) + return glnx_throw (error, "no signatures in static-delta"); + + if (out_value) + signatures = g_ptr_array_new_with_free_func (g_free); + + /* Check if there are signatures in the superblock */ + g_variant_iter_init (&iter, delta_sign); + while ((item = g_variant_iter_next_value (&iter))) + { + g_autoptr(GVariant) key_v = g_variant_get_child_value (item, 0); + const char *str = g_variant_get_string (key_v, NULL); + if (g_str_has_prefix (str, "ostree.sign.")) + { + ret = TRUE; + if (signatures) + g_ptr_array_add (signatures, g_strdup (str + strlen ("ostree.sign."))); + } + g_variant_unref (item); + } + + if (out_value && ret) + ot_transfer_out_value (out_value, &signatures); + + return ret; +} + /** * ostree_repo_static_delta_execute_offline: * @self: Repo @@ -895,3 +950,76 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, return TRUE; } + +/** + * ostree_repo_static_delta_verify_signature: + * @self: Repo + * @delta_id: delta path + * @sign: Signature engine used to check superblock + * @out_success_message: success message + * @error: Error + * + * Verify static delta file signature. + * + * Returns: TRUE if the signature of static delta file is valid using the + * signature engine provided, FALSE otherwise. + * + * Since: 2020.1 + */ +gboolean +ostree_repo_static_delta_verify_signature (OstreeRepo *self, + const char *delta_id, + OstreeSign *sign, + char **out_success_message, + GError **error) +{ + g_autoptr(GVariantBuilder) desc_sign_builder = NULL; + g_autoptr(GVariant) delta_meta = NULL; + glnx_autofd int delta_fd = -1; + + if (strchr (delta_id, '/')) + { + if (!glnx_openat_rdonly (AT_FDCWD, delta_id, TRUE, &delta_fd, error)) + return FALSE; + } + else + { + g_autofree char *from = NULL; + g_autofree char *to = NULL; + if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) + return FALSE; + + g_autofree char *delta_path = _ostree_get_relative_static_delta_superblock_path (from, to); + if (!glnx_openat_rdonly (self->repo_dir_fd, delta_path, TRUE, &delta_fd, error)) + return FALSE; + } + + if (!_ostree_repo_static_delta_is_signed (self, delta_fd, NULL, error)) + return FALSE; + + g_autoptr(GVariant) delta = NULL; + if (!ot_variant_read_fd (delta_fd, 0, + (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + /* Check if there are signatures for signature engine */ + const gchar *signature_key = ostree_sign_metadata_key(sign); + GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(sign); + delta_meta = g_variant_get_child_value (delta, 2); + if (delta_meta == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GVariant) signatures = g_variant_lookup_value (delta_meta, + signature_key, + signature_format); + if (!signatures) + return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key); + + /* Get static delta superblock */ + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + if (child == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes(child); + + return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error); +} diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index e28af29c..552c1e61 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -32,6 +32,7 @@ #include "ostree-repo-finder.h" #include "ostree-sepolicy.h" #include "ostree-gpg-verify-result.h" +#include "ostree-sign.h" G_BEGIN_DECLS @@ -1074,6 +1075,13 @@ gboolean ostree_repo_static_delta_execute_offline (OstreeRepo GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_repo_static_delta_verify_signature (OstreeRepo *self, + const char *delta_id, + OstreeSign *sign, + char **out_success_message, + GError **error); + _OSTREE_PUBLIC GHashTable *ostree_repo_traverse_new_reachable (void); From 512db0435c506f1d11169dc6f312dd954b06cd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 29 Nov 2019 12:40:11 +0100 Subject: [PATCH 04/12] bin/static-delta: Add command to verify delta signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new "static-delta verify" sub-command. This supports multiple keys to verify the static-delta file. Signed-off-by: Frédéric Danis --- bash/ostree | 34 +++++++++++++ man/ostree-static-delta.xml | 64 +++++++++++++++++++++++ src/ostree/ot-builtin-static-delta.c | 76 ++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) diff --git a/bash/ostree b/bash/ostree index a7389bd7..34a38b20 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1709,6 +1709,40 @@ _ostree_static_delta_show() { return 0 } +_ostree_static_delta_verify() { + local boolean_options=" + $main_boolean_options + " + + local options_with_args=" + --sign-type + --keys-file + --keys-dir + --repo + " + + local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) + + case "$prev" in + --keys-file|--keys-dir|--repo) + __ostree_compreply_dirs_only + return 0 + ;; + $options_with_args_glob ) + return 0 + ;; + esac + + case "$cur" in + -*) + local all_options="$boolean_options $options_with_args" + __ostree_compreply_all_options + ;; + esac + + return 0 +} + _ostree_static_delta() { local subcommands=" apply-offline diff --git a/man/ostree-static-delta.xml b/man/ostree-static-delta.xml index a4bef237..66fc7590 100644 --- a/man/ostree-static-delta.xml +++ b/man/ostree-static-delta.xml @@ -65,6 +65,9 @@ Boston, MA 02111-1307, USA. ostree static-delta apply-offline PATH + + ostree static-delta verify OPTIONS STATIC-DELTA KEY-ID + @@ -149,6 +152,67 @@ Boston, MA 02111-1307, USA. + + 'Verify' Options + + + + + + + + + + + base64-encoded public key for verifying. + + + + + + + ASCII-string used as public key. + + + + + + + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + The default is ed25519. + + + + + + + Read key(s) from file filename. + + + + Valid for ed25519 signature type. + For ed25519 this file must contain base64-encoded + public key(s) per line for verifying. + + + + + + + Redefine the system path, where to search files and subdirectories with + well-known and revoked keys. + + + + + Example diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c index d5e93783..4e507e7d 100644 --- a/src/ostree/ot-builtin-static-delta.c +++ b/src/ostree/ot-builtin-static-delta.c @@ -43,6 +43,7 @@ static gboolean opt_if_not_exists; static char **opt_key_ids; static char *opt_sign_name; static char *opt_keysfilename; +static char *opt_keysdir; #define BUILTINPROTO(name) static gboolean ot_static_delta_builtin_ ## name (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) @@ -51,6 +52,7 @@ BUILTINPROTO(show); BUILTINPROTO(delete); BUILTINPROTO(generate); BUILTINPROTO(apply_offline); +BUILTINPROTO(verify); #undef BUILTINPROTO @@ -70,6 +72,9 @@ static OstreeCommand static_delta_subcommands[] = { { "apply-offline", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_apply_offline, "Apply static delta file" }, + { "verify", OSTREE_BUILTIN_FLAG_NONE, + ot_static_delta_builtin_verify, + "Verify static delta signatures" }, { NULL, 0, NULL, NULL } }; @@ -107,6 +112,15 @@ static GOptionEntry list_options[] = { { NULL } }; +static GOptionEntry verify_options[] = { + { "sign-type", 0, 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_keysfilename, "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 static_delta_usage (char **argv, gboolean is_error) @@ -439,6 +453,68 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc return TRUE; } +static gboolean +ot_static_delta_builtin_verify (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) +{ + g_autoptr (GOptionContext) context = g_option_context_new ("STATIC-DELTA-FILE [KEY-ID...]"); + g_autoptr (OstreeRepo) repo = NULL; + gboolean verified; + char **key_ids; + int n_key_ids; + + if (!ostree_option_context_parse (context, verify_options, &argc, &argv, invocation, &repo, cancellable, error)) + return FALSE; + + if (argc < 3) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "DELTA must be specified"); + return FALSE; + } + + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; + + const char *delta_id = argv[2]; + + g_autoptr (OstreeSign) sign = ostree_sign_get_by_name (opt_sign_name, error); + if (!sign) + { + g_print("Sign-type not supported\n"); + return FALSE; + } + + key_ids = argv + 3; + n_key_ids = argc - 3; + for (int i = 0; i < n_key_ids; i++) + { + g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); + if (!ostree_sign_add_pk(sign, pk, error)) + return FALSE; + } + if ((n_key_ids == 0) || opt_keysfilename) + { + g_autoptr (GVariantBuilder) builder = NULL; + g_autoptr (GVariant) 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_keysfilename) + g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); + options = g_variant_builder_end (builder); + + if (!ostree_sign_load_pk (sign, options, error)) + return FALSE; + } + + verified = ostree_repo_static_delta_verify_signature (repo, delta_id, sign, NULL, error); + g_print ("Verification %s\n", verified ? "OK" : "fails"); + + return verified; +} + gboolean ostree_builtin_static_delta (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { From 0c48423c267ff80fae1c2918a0e398d5fd292789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 9 Jul 2020 17:34:08 +0200 Subject: [PATCH 05/12] lib/deltas: Support signed delta in execute_offline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This checks if the static delta file is signed or not to be able to correctly get the superblock to apply. Signed-off-by: Frédéric Danis --- src/libostree/ostree-repo-static-delta-core.c | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index 857473db..c71378fa 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -286,6 +286,8 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, GError **error) { g_autofree char *basename = NULL; + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) meta = NULL; const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file); @@ -311,10 +313,24 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, if (meta_fd < 0) return glnx_throw_errno_prefix (error, "openat(%s)", basename); - g_autoptr(GVariant) meta = NULL; - if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), - FALSE, &meta, error)) - return FALSE; + gboolean is_signed = _ostree_repo_static_delta_is_signed (self, meta_fd, NULL, NULL); + if (is_signed) + { + if (!ot_variant_read_fd (meta_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes (child); + meta = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + bytes, FALSE); + } + else + { + if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), + FALSE, &meta, error)) + return FALSE; + } /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ From bf0c09ffe19df69cb836aff7421db717e5f0ce48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 9 Jul 2020 17:35:00 +0200 Subject: [PATCH 06/12] lib/deltas: Support signed delta in dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This checks if the static delta file is signed or not to be able to correctly get the superblock to dump. Signed-off-by: Frédéric Danis --- src/libostree/ostree-repo-static-delta-core.c | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index c71378fa..8d0f4e82 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -819,6 +819,8 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, GError **error) { glnx_autofd int superblock_fd = -1; + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) delta_superblock = NULL; if (strchr (delta_id, '/')) { @@ -837,13 +839,28 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, return FALSE; } - g_autoptr(GVariant) delta_superblock = NULL; - if (!ot_variant_read_fd (superblock_fd, 0, - (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, - TRUE, &delta_superblock, error)) - return FALSE; + gboolean is_signed = _ostree_repo_static_delta_is_signed(self, superblock_fd, NULL, NULL); + if (is_signed) + { + if (!ot_variant_read_fd (superblock_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes(child); + delta_superblock = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + bytes, FALSE); + } + else + { + if (!ot_variant_read_fd (superblock_fd, 0, + (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + TRUE, &delta_superblock, error)) + return FALSE; + } g_print ("Delta: %s\n", delta_id); + g_print ("Signed: %s\n", is_signed ? "yes" : "no"); g_autoptr(GVariant) from_commit_v = NULL; g_variant_get_child (delta_superblock, 2, "@ay", &from_commit_v); g_autofree char *from_commit = NULL; From c98a993c996713e16af8e36cbbde63f6000d1341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 29 Nov 2019 16:17:17 +0100 Subject: [PATCH 07/12] tests/delta: new tests for signed deltas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests to generate signed deltas and verify them using 'dummy' signature engine. Signed-off-by: Frédéric Danis --- Makefile-tests.am | 1 + tests/test-delta-sign.sh | 131 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100755 tests/test-delta-sign.sh diff --git a/Makefile-tests.am b/Makefile-tests.am index a4179377..e463178b 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -114,6 +114,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-reset-nonlinear.sh \ tests/test-oldstyle-partial.sh \ tests/test-delta.sh \ + tests/test-delta-sign.sh \ tests/test-xattrs.sh \ tests/test-auto-summary.sh \ tests/test-prune.sh \ diff --git a/tests/test-delta-sign.sh b/tests/test-delta-sign.sh new file mode 100755 index 00000000..b9854ce7 --- /dev/null +++ b/tests/test-delta-sign.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# +# Copyright (C) 2011,2013 Colin Walters +# +# 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +skip_without_user_xattrs + +bindatafiles="bash true ostree" + +echo '1..3' + +# This is explicitly opt in for testing +export OSTREE_DUMMY_SIGN_ENABLED=1 + +mkdir repo +ostree_repo_init repo --mode=archive + +mkdir files +for bin in ${bindatafiles}; do + cp $(which ${bin}) files +done + +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +function permuteFile() { + permutation=$(($1 % 2)) + output=$2 + case $permutation in + 0) dd if=/dev/zero count=40 bs=1 >> $output;; + 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;; + esac +} + +function permuteDirectory() { + permutation=$1 + dir=$2 + for x in ${dir}/*; do + for z in $(seq ${permutation}); do + permuteFile ${z} ${x} + done + done +} + +get_assert_one_direntry_matching() { + local path=$1 + local r=$2 + local child="" + local bn + for p in ${path}/*; do + bn=$(basename $p) + if ! echo ${bn} | grep -q "$r"; then + continue + fi + if test -z "${child}"; then + child=${bn} + else + assert_not_reached "Expected only one child matching ${r} in ${path}"; + fi + done + if test -z "${child}"; then + assert_not_reached "Failed to find child matching ${r}" + fi + echo ${child} +} + +origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +permuteDirectory 1 files +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-not-signed.txt 2>&1 && exit 1 +assert_file_has_content show-not-signed.txt "Verification fails" +assert_file_has_content show-not-signed.txt "no signatures in static-delta" + +deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.') +deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-') + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-inline-not-signed.txt 2>&1 && exit 1 +assert_file_has_content show-not-signed.txt "Verification fails" +assert_file_has_content show-not-signed.txt "no signatures in static-delta" + +echo 'ok verify ok with unsigned deltas' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-signed.txt +assert_file_has_content show-dummy-signed.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-inline-signed.txt +assert_file_has_content show-dummy-inline-signed.txt "Verification OK" + +echo 'ok verified with dummy' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-signed.txt && exit 1 +assert_file_has_content show-dummy-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-inline-signed.txt && exit 1 +assert_file_has_content show-dummy-bad-inline-signed.txt "Verification fails" + +echo 'ok verification failed with dummy and bad key' From 96bcc25632fa0b1b4de637845bf43535f4eb3628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 23 Apr 2020 15:24:53 +0200 Subject: [PATCH 08/12] tests/libtest.sh: Add skip_without_sign_ed25519() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Frédéric Danis --- tests/libtest.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/libtest.sh b/tests/libtest.sh index ca457fa2..7c66a5c6 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -700,6 +700,12 @@ has_sign_ed25519 () { return ${ret} } +skip_without_sign_ed25519() { + if ! has_sign_ed25519; then + skip "no ed25519 support compiled in" + fi +} + # Keys for ed25519 signing tests ED25519PUBLIC= ED25519SEED= From 869dbc037e92fbe2d859e8fda97bc4a287160770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 3 Dec 2019 11:15:51 +0100 Subject: [PATCH 09/12] tests/delta: new tests for 'ed25519' signed deltas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests to generate signed deltas and verify them using 'ed25519' signature engine. Signed-off-by: Frédéric Danis --- Makefile-tests.am | 1 + tests/test-delta-ed25519.sh | 283 ++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100755 tests/test-delta-ed25519.sh diff --git a/Makefile-tests.am b/Makefile-tests.am index e463178b..3e4f17a5 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -115,6 +115,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-oldstyle-partial.sh \ tests/test-delta.sh \ tests/test-delta-sign.sh \ + tests/test-delta-ed25519.sh \ tests/test-xattrs.sh \ tests/test-auto-summary.sh \ tests/test-prune.sh \ diff --git a/tests/test-delta-ed25519.sh b/tests/test-delta-ed25519.sh new file mode 100755 index 00000000..e50b9763 --- /dev/null +++ b/tests/test-delta-ed25519.sh @@ -0,0 +1,283 @@ +#!/bin/bash +# +# Copyright (C) 2011,2013 Colin Walters +# +# 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +skip_without_user_xattrs + +skip_without_sign_ed25519 + +bindatafiles="bash true ostree" + +echo '1..9' + +mkdir repo +ostree_repo_init repo --mode=archive + +mkdir files +for bin in ${bindatafiles}; do + cp $(which ${bin}) files +done + +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +function permuteFile() { + permutation=$(($1 % 2)) + output=$2 + case $permutation in + 0) dd if=/dev/zero count=40 bs=1 >> $output;; + 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;; + esac +} + +function permuteDirectory() { + permutation=$1 + dir=$2 + for x in ${dir}/*; do + for z in $(seq ${permutation}); do + permuteFile ${z} ${x} + done + done +} + +get_assert_one_direntry_matching() { + local path=$1 + local r=$2 + local child="" + local bn + for p in ${path}/*; do + bn=$(basename $p) + if ! echo ${bn} | grep -q "$r"; then + continue + fi + if test -z "${child}"; then + child=${bn} + else + assert_not_reached "Expected only one child matching ${r} in ${path}"; + fi + done + if test -z "${child}"; then + assert_not_reached "Failed to find child matching ${r}" + fi + echo ${child} +} + +origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +permuteDirectory 1 files +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +# Test ostree sign with 'ed25519' module +gen_ed25519_keys +PUBLIC=${ED25519PUBLIC} +SEED=${ED25519SEED} +SECRET=${ED25519SECRET} +WRONG_PUBLIC="$(gen_ed25519_random_public)" + +SECRETKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)" +echo ${SECRET} > ${SECRETKEYS} + +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-signed-1.txt +assert_file_has_content show-ed25519-key-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-signed-2.txt +assert_file_has_content show-ed25519-key-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-signed-3.txt +assert_file_has_content show-ed25519-key-signed-3.txt "Verification OK" + +deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.') +deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-') + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-inline-signed-1.txt +assert_file_has_content show-ed25519-key-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-inline-signed-2.txt +assert_file_has_content show-ed25519-key-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-inline-signed-3.txt +assert_file_has_content show-ed25519-key-inline-signed-3.txt "Verification OK" + +echo 'ok verified with ed25519 (sign - key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-signed-1.txt +assert_file_has_content show-ed25519-keyfile-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-signed-2.txt +assert_file_has_content show-ed25519-keyfile-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-signed-3.txt +assert_file_has_content show-ed25519-keyfile-signed-3.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-inline-signed-1.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-inline-signed-2.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-inline-signed-3.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-3.txt "Verification OK" + +echo 'ok verified with ed25519 (keyfile - key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-key-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-key-bad-inline-signed.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (sign - bad key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-keyfile-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-keyfile-bad-inline-signed.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (keyfile - bad key)' + +# Prepare files with public ed25519 signatures +PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)" +for((i=0;i<100;i++)); do + # Generate a list with some public signatures + gen_ed25519_random_public +done > ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-1.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-1.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-2.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-2.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-1.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-1.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-2.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-2.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (sign - bad keys)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-3.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-3.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-4.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-4.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-3.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-3.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-4.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-4.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (keyfile - bad keys)' + +# Add correct key into the list +echo ${PUBLIC} >> ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-1.txt +assert_file_has_content show-ed25519-file-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-2.txt +assert_file_has_content show-ed25519-file-signed-2.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-1.txt +assert_file_has_content show-ed25519-file-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-2.txt +assert_file_has_content show-ed25519-file-inline-signed-2.txt "Verification OK" + +echo 'ok verified with ed25519 (sign - file)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-3.txt +assert_file_has_content show-ed25519-file-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-4.txt +assert_file_has_content show-ed25519-file-signed-4.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-3.txt +assert_file_has_content show-ed25519-file-inline-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-4.txt +assert_file_has_content show-ed25519-file-inline-signed-4.txt "Verification OK" + +echo 'ok verified with ed25519 (keyfile - file)' + +# Test ostree sign with multiple 'ed25519' keys +gen_ed25519_keys +PUBLIC2=${ED25519PUBLIC} +SEED2=${ED25519SEED} +SECRET2=${ED25519SECRET} + +echo ${SECRET2} >> ${SECRETKEYS} +echo ${PUBLIC2} >> ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-signed-1.txt +assert_file_has_content show-ed25519-multiplekeys-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-signed-2.txt +assert_file_has_content show-ed25519-multiplekeys-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-multiplekeys-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-signed-3.txt +assert_file_has_content show-ed25519-multiplekeys-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-signed-4.txt +assert_file_has_content show-ed25519-multiplekeys-signed-4.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-inline-signed-1.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-inline-signed-2.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-multiplekeys-bad-inline-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-inline-signed-3.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-inline-signed-4.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-4.txt "Verification OK" + +echo 'ok verified with ed25519 (multiple keys)' From fb1faf17d6f9cefd349c46f48f7a28f269f07576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 25 Aug 2020 09:26:09 +0200 Subject: [PATCH 10/12] lib/deltas: Check signed delta in execute_offline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new function `ostree_repo_static_delta_execute_offline_with_signature` which takes a signature engine to verify the delta before applying it. The `ostree_repo_static_delta_execute_offline` is just a wrapper to this new function, passing a NULL signature engine. When this function is called without signature engine, but with a sign delta, it will only fails if `sign-verify-deltas` is set to true in repo core options. This commits move signature existence check and delta signature verification to share common parts between existing APIs and the new function. Signed-off-by: Frédéric Danis --- apidoc/ostree-sections.txt | 1 + src/libostree/libostree-devel.sym | 1 + src/libostree/ostree-repo-static-delta-core.c | 132 +++++++++++++----- src/libostree/ostree-repo.h | 8 ++ 4 files changed, 108 insertions(+), 34 deletions(-) diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 8cafeb1f..66b42233 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -412,6 +412,7 @@ ostree_repo_list_commit_objects_starting_with ostree_repo_list_static_delta_names OstreeStaticDeltaGenerateOpt ostree_repo_static_delta_generate +ostree_repo_static_delta_execute_offline_with_signature ostree_repo_static_delta_execute_offline ostree_repo_static_delta_verify_signature ostree_repo_traverse_new_reachable diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 9a847b92..a15654c1 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -22,6 +22,7 @@ global: /* Add symbols here, and uncomment the bits in * Makefile-libostree.am to enable this too. */ + ostree_repo_static_delta_execute_offline_with_signature; ostree_repo_static_delta_verify_signature; } LIBOSTREE_2020.4; diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index 8d0f4e82..b16764ae 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -265,25 +265,68 @@ _ostree_repo_static_delta_is_signed (OstreeRepo *self, return ret; } +static gboolean +_ostree_repo_static_delta_verify_signature (OstreeRepo *self, + int fd, + OstreeSign *sign, + char **out_success_message, + GError **error) +{ + g_autoptr(GVariantBuilder) desc_sign_builder = NULL; + g_autoptr(GVariant) delta_meta = NULL; + g_autoptr(GVariant) delta = NULL; + + if (!ot_variant_read_fd (fd, 0, + (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + /* Check if there are signatures for signature engine */ + const gchar *signature_key = ostree_sign_metadata_key(sign); + GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(sign); + delta_meta = g_variant_get_child_value (delta, 2); + if (delta_meta == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GVariant) signatures = g_variant_lookup_value (delta_meta, + signature_key, + signature_format); + if (!signatures) + return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key); + + /* Get static delta superblock */ + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + if (child == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes(child); + + return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error); +} + /** - * ostree_repo_static_delta_execute_offline: + * ostree_repo_static_delta_execute_offline_with_signature: * @self: Repo * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock + * @sign: Signature engine used to check superblock * @skip_validation: If %TRUE, assume data integrity * @cancellable: Cancellable * @error: Error * * Given a directory representing an already-downloaded static delta - * on disk, apply it, generating a new commit. The directory must be - * named with the form "FROM-TO", where both are checksums, and it - * must contain a file named "superblock", along with at least one part. + * on disk, apply it, generating a new commit. + * If sign is passed, the static delta signature is verified. + * If sign-verify-deltas configuration option is set and static delta is signed, + * signature verification will be mandatory before apply the static delta. + * The directory must be named with the form "FROM-TO", where both are + * checksums, and it must contain a file named "superblock", along with at least + * one part. */ gboolean -ostree_repo_static_delta_execute_offline (OstreeRepo *self, - GFile *dir_or_file, - gboolean skip_validation, - GCancellable *cancellable, - GError **error) +ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, + GFile *dir_or_file, + OstreeSign *sign, + gboolean skip_validation, + GCancellable *cancellable, + GError **error) { g_autofree char *basename = NULL; g_autoptr(GVariant) delta = NULL; @@ -316,6 +359,25 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, gboolean is_signed = _ostree_repo_static_delta_is_signed (self, meta_fd, NULL, NULL); if (is_signed) { + gboolean verify_deltas; + gboolean verified; + + if (!ot_keyfile_get_boolean_with_default (self->config, "core", "sign-verify-deltas", + FALSE, &verify_deltas, error)) + return FALSE; + + if (verify_deltas && !sign) + return glnx_throw (error, "Key is mandatory to check delta signature"); + + if (sign) + { + verified = _ostree_repo_static_delta_verify_signature (self, meta_fd, sign, NULL, error); + if (*error) + return FALSE; + if (!verified) + return glnx_throw (error, "Delta signature verification failed"); + } + if (!ot_variant_read_fd (meta_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta, error)) return FALSE; @@ -479,6 +541,32 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, return TRUE; } +/** + * ostree_repo_static_delta_execute_offline: + * @self: Repo + * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock + * @skip_validation: If %TRUE, assume data integrity + * @cancellable: Cancellable + * @error: Error + * + * Given a directory representing an already-downloaded static delta + * on disk, apply it, generating a new commit. The directory must be + * named with the form "FROM-TO", where both are checksums, and it + * must contain a file named "superblock", along with at least one part. + */ +gboolean +ostree_repo_static_delta_execute_offline (OstreeRepo *self, + GFile *dir_or_file, + gboolean skip_validation, + GCancellable *cancellable, + GError **error) +{ + return ostree_repo_static_delta_execute_offline_with_signature(self, dir_or_file, NULL, + skip_validation, + cancellable, + error); +} + gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, @@ -1030,29 +1118,5 @@ ostree_repo_static_delta_verify_signature (OstreeRepo *self, if (!_ostree_repo_static_delta_is_signed (self, delta_fd, NULL, error)) return FALSE; - g_autoptr(GVariant) delta = NULL; - if (!ot_variant_read_fd (delta_fd, 0, - (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, - TRUE, &delta, error)) - return FALSE; - - /* Check if there are signatures for signature engine */ - const gchar *signature_key = ostree_sign_metadata_key(sign); - GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(sign); - delta_meta = g_variant_get_child_value (delta, 2); - if (delta_meta == NULL) - return glnx_throw (error, "no metadata in static-delta superblock"); - g_autoptr(GVariant) signatures = g_variant_lookup_value (delta_meta, - signature_key, - signature_format); - if (!signatures) - return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key); - - /* Get static delta superblock */ - g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); - if (child == NULL) - return glnx_throw (error, "no metadata in static-delta superblock"); - g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes(child); - - return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error); + return _ostree_repo_static_delta_verify_signature (self, delta_fd, sign, out_success_message, error); } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 552c1e61..d52fc9c3 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -1068,6 +1068,14 @@ gboolean ostree_repo_static_delta_generate (OstreeRepo *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, + GFile *dir_or_file, + OstreeSign *sign, + gboolean skip_validation, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC gboolean ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file, From 2e97f5659ffdc52bcdd5906d2e98fb1013b3c2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 21 Aug 2020 17:22:40 +0200 Subject: [PATCH 11/12] bin/static-delta: Add signature parameters to apply-offline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to check the delta signature before applying it. Signed-off-by: Frédéric Danis --- bash/ostree | 3 ++ man/ostree-static-delta.xml | 61 +++++++++++++++++++++++++++- src/ostree/ot-builtin-static-delta.c | 56 ++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/bash/ostree b/bash/ostree index 34a38b20..d00695ef 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1532,6 +1532,9 @@ _ostree_static_delta_apply_offline() { " local options_with_args=" + --sign-type + --keys-file + --keys-dir --repo " diff --git a/man/ostree-static-delta.xml b/man/ostree-static-delta.xml index 66fc7590..440ada41 100644 --- a/man/ostree-static-delta.xml +++ b/man/ostree-static-delta.xml @@ -63,7 +63,7 @@ Boston, MA 02111-1307, USA. ostree static-delta generate --to=REV OPTIONS - ostree static-delta apply-offline PATH + ostree static-delta apply-offline OPTIONS PATH KEY-ID ostree static-delta verify OPTIONS STATIC-DELTA KEY-ID @@ -152,6 +152,65 @@ Boston, MA 02111-1307, USA. + + 'Apply-offline' Options + + + + + + + + + + + base64-encoded public key for verifying. + + + + + + + ASCII-string used as public key. + + + + + + + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + + + + + + Read key(s) from file filename. + + + + Valid for ed25519 signature type. + For ed25519 this file must contain base64-encoded + public key(s) per line for verifying. + + + + + + + Redefine the system path, where to search files and subdirectories with + well-known and revoked keys. + + + + + 'Verify' Options diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c index 4e507e7d..3e0af5bd 100644 --- a/src/ostree/ot-builtin-static-delta.c +++ b/src/ostree/ot-builtin-static-delta.c @@ -105,6 +105,11 @@ static GOptionEntry generate_options[] = { }; static GOptionEntry apply_offline_options[] = { + { "sign-type", 0, 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_keysfilename, "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 } }; @@ -423,6 +428,9 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc { g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeRepo) repo = NULL; + g_autoptr (OstreeSign) sign = NULL; + char **key_ids; + int n_key_ids; context = g_option_context_new (""); if (!ostree_option_context_parse (context, apply_offline_options, &argc, &argv, invocation, &repo, cancellable, error)) @@ -438,13 +446,59 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc return FALSE; } +#if defined(HAVE_LIBSODIUM) + /* Initialize crypto system */ + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; +#endif + + if (opt_sign_name) + { + sign = ostree_sign_get_by_name (opt_sign_name, error); + if (!sign) + return glnx_throw (error, "Signing type %s is not supported", opt_sign_name); + + key_ids = argv + 3; + n_key_ids = argc - 3; + for (int i = 0; i < n_key_ids; i++) + { + g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); + if (!ostree_sign_add_pk(sign, pk, error)) + return FALSE; + } + if ((n_key_ids == 0) || opt_keysfilename) + { + g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr (GVariant) options = NULL; + + /* 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_keysfilename) + g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); + options = g_variant_builder_end (builder); + + if (!ostree_sign_load_pk (sign, options, error)) + { + /* If it fails to load system default public keys, consider there no signature engine */ + if (!opt_keysdir && !opt_keysfilename) + { + g_clear_error(error); + g_clear_object(&sign); + } + else + return FALSE; + } + } + } + const char *patharg = argv[2]; g_autoptr(GFile) path = g_file_new_for_path (patharg); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; - if (!ostree_repo_static_delta_execute_offline (repo, path, FALSE, cancellable, error)) + if (!ostree_repo_static_delta_execute_offline_with_signature (repo, path, sign, FALSE, cancellable, error)) return FALSE; if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) From ecbfe08ec75497767b76a962319f7bff6449da0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 21 Aug 2020 17:24:49 +0200 Subject: [PATCH 12/12] tests/delta: Add new tests for applying signed deltas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new test to apply offline signed deltas. Signed-off-by: Frédéric Danis --- tests/test-delta-ed25519.sh | 41 ++++++++++++++++++++++++++++++++- tests/test-delta-sign.sh | 45 ++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/tests/test-delta-ed25519.sh b/tests/test-delta-ed25519.sh index e50b9763..ef732cf9 100755 --- a/tests/test-delta-ed25519.sh +++ b/tests/test-delta-ed25519.sh @@ -29,7 +29,7 @@ skip_without_sign_ed25519 bindatafiles="bash true ostree" -echo '1..9' +echo '1..12' mkdir repo ostree_repo_init repo --mode=archive @@ -281,3 +281,42 @@ ${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origr assert_file_has_content show-ed25519-multiplekeys-inline-signed-4.txt "Verification OK" echo 'ok verified with ed25519 (multiple keys)' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=ed25519 --keys-file=${PUBKEYS} repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with ed25519 (keyfile)' + +mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with ed25519 (keydir)' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +if ${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir}; then + exit 1 +fi + +rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d + +echo 'ok apply offline with ed25519 revoking key mechanism (keydir)' diff --git a/tests/test-delta-sign.sh b/tests/test-delta-sign.sh index b9854ce7..86f12f96 100755 --- a/tests/test-delta-sign.sh +++ b/tests/test-delta-sign.sh @@ -27,7 +27,7 @@ skip_without_user_xattrs bindatafiles="bash true ostree" -echo '1..3' +echo '1..7' # This is explicitly opt in for testing export OSTREE_DUMMY_SIGN_ENABLED=1 @@ -129,3 +129,46 @@ ${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev assert_file_has_content show-dummy-bad-inline-signed.txt "Verification fails" echo 'ok verification failed with dummy and bad key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with no signature verification and no key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 config set core.sign-verify-deltas true +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir} 2> apply-offline-verification-no-key.txt && exit 1 +assert_file_has_content apply-offline-verification-no-key.txt "Key is mandatory to check delta signature" + +echo 'ok apply offline failed with signature verification forced and no key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} dummysign +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with dummy' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} badsign 2> apply-offline-bad-key.txt && exit 1 +assert_file_has_content apply-offline-bad-key.txt "signature: dummy: incorrect signature" + +echo 'ok apply offline failed with dummy and bad key'