From bd99da263376b1e9bfed0fe4fc9d9bb4daea4c65 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Jul 2023 13:01:45 +0200 Subject: [PATCH] WIP: Initial work on user-space signatures --- Makefile-libostree.am | 6 +-- Makefile-switchroot.am | 5 +++ src/libostree/libostree-devel.sym | 5 +++ src/libostree/ostree-repo-composefs.c | 48 +++++++++++++++++++++++ src/libostree/ostree-repo-private.h | 1 + src/libostree/ostree-repo.h | 3 ++ src/libostree/ostree-sysroot-deploy.c | 14 +++++++ src/ostree/ot-builtin-commit.c | 21 +++++++++- src/switchroot/ostree-prepare-root.c | 56 ++++++++++++++++++++++++++- 9 files changed, 154 insertions(+), 5 deletions(-) diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 8edd7f4de1..438741de7a 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -174,9 +174,9 @@ endif # USE_GPGME symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym # Uncomment this include when adding new development symbols. -#if BUILDOPT_IS_DEVEL_BUILD -#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym -#endif +if BUILDOPT_IS_DEVEL_BUILD +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/Makefile-switchroot.am b/Makefile-switchroot.am index 09f954f381..bf0f94470e 100644 --- a/Makefile-switchroot.am +++ b/Makefile-switchroot.am @@ -53,6 +53,11 @@ ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composef ostree_prepare_root_SOURCES += src/switchroot/ostree-prepare-root.c ostree_prepare_root_CPPFLAGS += $(OT_INTERNAL_GIO_UNIX_CFLAGS) -I $(srcdir)/libglnx ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libglnx.la +if USE_LIBSODIUM +ostree_prepare_root_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS) +ostree_prepare_root_LDADD += $(OT_DEP_LIBSODIUM_LIBS) +endif # USE_LIBSODIUM + endif # BUILDOPT_USE_STATIC_COMPILER diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 9168db734a..f50752b998 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -29,3 +29,8 @@ global: someostree_symbol_deleteme; } LIBOSTREE_2021.$LASTSTABLE; */ + +LIBOSTREE_2023.6 { +global: + ostree_composefs_sign_metadata; +} LIBOSTREE_2023.4; diff --git a/src/libostree/ostree-repo-composefs.c b/src/libostree/ostree-repo-composefs.c index c6ce5787c9..958b721565 100644 --- a/src/libostree/ostree-repo-composefs.c +++ b/src/libostree/ostree-repo-composefs.c @@ -26,6 +26,8 @@ #include "ostree-core-private.h" #include "ostree-repo-file.h" #include "ostree-repo-private.h" +#include "ostree-sign.h" +#include "ostree-autocleanups.h" #ifdef HAVE_COMPOSEFS #include @@ -612,3 +614,49 @@ ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_versio return composefs_not_supported (error); #endif } + +/** + * ostree_composefs_sign_metadata: + * @dict: A GVariant builder of type a{sv} + * @secret_key: ed25519 secret key to use + * @cancellable: Cancellable + * @error: Error + * + * After ostree_repo_commit_add_composefs_metadata() has added the + * composefs digest to the metadata dict, this can be called to add + * an ed25519 signature to the digest. This signature will be written + * out during deploy and can be verified at boot. + */ +_OSTREE_PUBLIC +gboolean +ostree_composefs_sign_metadata (GVariantDict *dict, GVariant *secret_key, + GCancellable *cancellable, GError **error) +{ + g_autoptr (GVariant) digest = NULL; + g_autoptr (GBytes) digest_bytes = NULL; + g_autoptr (GBytes) signature = NULL; + g_autoptr (OstreeSign) sign = NULL; + + digest = g_variant_dict_lookup_value (dict, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING); + if (digest == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No composefs digest in metadata to sign"); + return FALSE; + } + + sign = ostree_sign_get_by_name (OSTREE_SIGN_NAME_ED25519, error); + if (sign == NULL) + return FALSE; + + if (!ostree_sign_set_sk (sign, secret_key, error)) + return FALSE; + + digest_bytes = g_variant_get_data_as_bytes (digest); + if (!ostree_sign_data (sign, digest_bytes, &signature, cancellable, error)) + return glnx_prefix_error (error, "Not able to sign the composefs digest"); + + g_variant_dict_insert_value (dict, OSTREE_COMPOSEFS_SIGN_KEY_V0, + ot_gvariant_new_ay_bytes (signature)); + + return TRUE; +} diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 6d3f21e61f..be5f6b0faa 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -69,6 +69,7 @@ G_BEGIN_DECLS #define OSTREE_COMPOSEFS_META_PREFIX "ostree.composefs" // The fs-verity digest of the composefs, version 0 #define OSTREE_COMPOSEFS_DIGEST_KEY_V0 OSTREE_COMPOSEFS_META_PREFIX ".digest.v0" +#define OSTREE_COMPOSEFS_SIGN_KEY_V0 OSTREE_COMPOSEFS_META_PREFIX ".sign.v0" #define _OSTREE_INTEGRITY_SECTION "ex-integrity" diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 2dea909223..28f3723a7f 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -694,6 +694,9 @@ _OSTREE_PUBLIC gboolean ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_version, GVariantDict *dict, OstreeRepoFile *repo_root, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_composefs_sign_metadata (GVariantDict *dict, GVariant *secret_key, + GCancellable *cancellable, GError **error); _OSTREE_PUBLIC gboolean ostree_repo_write_commit (OstreeRepo *self, const char *parent, const char *subject, diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 2454a5877d..75d0af0593 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -661,6 +661,8 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0); g_autoptr (GVariant) metadata_composefs = g_variant_lookup_value ( metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING); + g_autoptr (GVariant) metadata_composefs_sig = g_variant_lookup_value ( + metadata, OSTREE_COMPOSEFS_SIGN_KEY_V0, G_VARIANT_TYPE_BYTESTRING); /* Create a composefs image and put in deploy dir as .ostree.cfs */ g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new (); @@ -697,6 +699,18 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy error)) return FALSE; + if (metadata_composefs && metadata_composefs_sig) + { + g_autofree char *composefs_sig_path + = g_strdup_printf ("%s/.ostree.cfs.sig", checkout_target_name); + g_autoptr (GBytes) sig = g_variant_get_data_as_bytes (metadata_composefs_sig); + + if (!glnx_file_replace_contents_at (osdeploy_dfd, composefs_sig_path, + g_bytes_get_data (sig, NULL), g_bytes_get_size (sig), + 0, cancellable, error)) + return FALSE; + } + /* This is where the erofs image will be temporarily mounted */ g_autofree char *composefs_mnt_path = g_strdup_printf ("%s/.ostree.mnt", checkout_target_name); diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 760496715a..7e9a543a1a 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -71,6 +71,7 @@ static char **opt_key_ids; static char *opt_sign_name; static gboolean opt_generate_sizes; static gboolean opt_composefs_metadata; +static char *opt_composefs_key_path; static gboolean opt_disable_fsync; static char *opt_timestamp; @@ -164,6 +165,8 @@ static GOptionEntry options[] = { "Generate size information along with commit metadata", NULL }, { "generate-composefs-metadata", 0, 0, G_OPTION_ARG_NONE, &opt_composefs_metadata, "Generate composefs commit metadata", NULL }, + { "sign-composefs", 0, 0, G_OPTION_ARG_STRING, &opt_composefs_key_path, + "Sign the composefs digest. Implies --generate-composefs-metadata", "PATH" }, { "disable-fsync", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_disable_fsync, "Do not invoke fsync()", NULL }, { "fsync", 0, 0, G_OPTION_ARG_CALLBACK, parse_fsync_cb, "Specify how to invoke fsync()", @@ -875,7 +878,7 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio metadata = g_variant_ref_sink (g_variant_dict_end (&bootmeta)); } - if (opt_composefs_metadata) + if (opt_composefs_metadata || opt_composefs_key_path) { g_autoptr (GVariant) old_metadata = g_steal_pointer (&metadata); g_auto (GVariantDict) newmeta; @@ -884,6 +887,22 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio repo, 0, &newmeta, OSTREE_REPO_FILE (root), cancellable, error)) goto out; + if (opt_composefs_key_path) + { + g_autofree char *key_data; + gsize key_size; + + if (!g_file_get_contents (opt_composefs_key_path, &key_data, &key_size, error)) + { + glnx_prefix_error (error, "Failed to open '%s'", opt_composefs_key_path); + goto out; + } + + g_autoptr (GVariant) secret_key = ot_gvariant_new_bytearray ((guchar *)key_data, key_size); + if (!ostree_composefs_sign_metadata (&newmeta,secret_key, cancellable, error)) + goto out; + } + metadata = g_variant_ref_sink (g_variant_dict_end (&newmeta)); } diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index ab8bb14227..facd6c4bd8 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -70,10 +70,16 @@ #include #include +#ifdef HAVE_LIBSODIUM +#include +#endif + /* We can't include both linux/fs.h and sys/mount.h, so define these directly */ #define FS_VERITY_FL 0x00100000 /* Verity protected inode */ #define FS_IOC_GETFLAGS _IOR ('f', 1, long) +#define SHA256_DIGEST_LEN (32) + // The name of the composefs metadata root #define OSTREE_COMPOSEFS_NAME ".ostree.cfs" @@ -167,6 +173,8 @@ main (int argc, char *argv[]) const char *root_arg = NULL; bool we_mounted_proc = false; + g_autoptr (GError) error = NULL; + if (argc < 2) err (EXIT_FAILURE, "usage: ostree-prepare-root SYSROOT"); root_arg = argv[1]; @@ -270,9 +278,54 @@ main (int argc, char *argv[]) objdirs, 1, }; + glnx_autofd int cfs_fd = -1; + + cfs_fd = open (OSTREE_COMPOSEFS_NAME, O_RDONLY | O_CLOEXEC); + if (cfs_fd == -1) + { +#ifdef USE_LIBSYSTEMD + sd_journal_send ("MESSAGE=Failed to open '%s': %s", OSTREE_COMPOSEFS_NAME, strerror (errno), NULL); +#endif + goto nocomposefs; + } if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED) - errx (EXIT_FAILURE, "composefs signature not supported"); + { + const char *pubkey_path = "/etc/ostree/composefs.pub"; + char buf[sizeof (struct fsverity_digest) + SHA256_DIGEST_LEN]; + struct fsverity_digest *d = (struct fsverity_digest *)&buf; + g_autofree char *pubkey; + gsize pubkey_size; + g_autofree char *sig; + gsize sig_size; + + if (!g_file_get_contents (OSTREE_COMPOSEFS_NAME ".sig", &sig, &sig_size, &error)) + err (EXIT_FAILURE, "failed to load '%s': %s", OSTREE_COMPOSEFS_NAME ".sig", error->message); + + if (sig_size != crypto_core_ed25519_HASHBYTES) + err (EXIT_FAILURE, "Invalid signature file '%s'", OSTREE_COMPOSEFS_NAME ".sig"); + + if (!g_file_get_contents (pubkey_path, &pubkey, &pubkey_size, &error)) + err (EXIT_FAILURE, "failed to load '%s': %s", pubkey_path, error->message); + + d->digest_size = SHA256_DIGEST_LEN; + if (ioctl (cfs_fd, FS_IOC_MEASURE_VERITY, d) < 0) + err (EXIT_FAILURE, "Failed to get fs-verity digest for '%s'", OSTREE_COMPOSEFS_NAME); + + if (d->digest_size != SHA256_DIGEST_LEN || + d->digest_algorithm != FS_VERITY_HASH_ALG_SHA256) + err (EXIT_FAILURE, "Invalid fs-verity digest type for '%s'", OSTREE_COMPOSEFS_NAME); + +#ifdef HAVE_LIBSODIUM + if (sodium_init () < 0) + err (EXIT_FAILURE, "Failed to init libsodiume"); + + if (crypto_sign_verify_detached ((unsigned char *)sig, d->digest, d->digest_size, (unsigned char *)pubkey) != 0) + err (EXIT_FAILURE, "Mismatched signature for composefs image"); +#else + err (EXIT_FAILURE, "libsodiume missin, signatures not supported") +#endif + } cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; @@ -318,6 +371,7 @@ main (int argc, char *argv[]) sd_journal_send ("MESSAGE=Mounting composefs image failed: %s", strerror (errno), NULL); #endif } + nocomposefs: #else err (EXIT_FAILURE, "Composefs not supported"); #endif