diff --git a/Makefile-switchroot.am b/Makefile-switchroot.am index 09f954f381..9833f7e0d0 100644 --- a/Makefile-switchroot.am +++ b/Makefile-switchroot.am @@ -49,10 +49,20 @@ ostree-prepare-root : $(ostree_prepare_root_SOURCES) CLEANFILES += ostree-prepare-root else ostree_boot_PROGRAMS += ostree-prepare-root -ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composefs +ostree_prepare_root_CFLAGS += $(AM_CFLAGS) -Isrc/switchroot -I$(srcdir)/composefs -I$(srcdir)/src/libostree -I$(srcdir)/src/libotutil 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 +ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) libotutil.la libglnx.la +if USE_LIBSODIUM +ostree_prepare_root_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS) +ostree_prepare_root_LDADD += $(OT_DEP_LIBSODIUM_LIBS) +else +if USE_OPENSSL +ostree_prepare_root_CFLAGS += $(OT_DEP_CRYPTO_CFLAGS) +ostree_prepare_root_LDADD += $(OT_DEP_CRYPTO_LIBS) +endif # USE_OPENSSL +endif # USE_LIBSODIUM + endif # BUILDOPT_USE_STATIC_COMPILER diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index ab8bb14227..8c007da8f5 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -70,6 +70,21 @@ #include #include +#include +#include + +#ifdef HAVE_LIBSODIUM +#include +#define USE_LIBSODIUM +#else + +#if defined(HAVE_OPENSSL) +#include +#define USE_OPENSSL +#endif + +#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) @@ -160,6 +175,98 @@ pivot_root (const char *new_root, const char *put_old) return syscall (__NR_pivot_root, new_root, put_old); } +static GVariant * +load_variant (const char *root_mountpoint, const char *digest, const char *extension, + const GVariantType *type, GError **error) +{ + g_autofree char *path = NULL; + char *data = NULL; + gsize data_size; + + path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint, digest, digest + 2, + extension); + + if (!g_file_get_contents (path, &data, &data_size, error)) + return NULL; + + return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data)); +} + +static gboolean +load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out, + GVariant **commitmeta_out, GError **error) +{ + g_autoptr (GError) local_error = NULL; + g_autofree char *digest = g_path_get_basename (deploy_path); + char *dot; + + dot = strchr (digest, '.'); + if (dot != NULL) + *dot = 0; + + g_autoptr (GVariant) commit_v + = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error); + if (commit_v == NULL) + return FALSE; + + g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta", + G_VARIANT_TYPE ("a{sv}"), &local_error); + if (commitmeta_v == NULL) + { + if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + glnx_throw (error, "No commitmeta for commit %s", digest); + else + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + *commit_out = g_steal_pointer (&commit_v); + *commitmeta_out = g_steal_pointer (&commitmeta_v); + + return TRUE; +} + +static gboolean +validate_signature (GBytes *data, GVariant *signatures, const guchar *pubkey, size_t pubkey_size) +{ +#if defined(USE_LIBSODIUM) + if (sodium_init () < 0) + err (EXIT_FAILURE, "Failed to init libsodiume"); +#endif + + for (gsize i = 0; i < g_variant_n_children (signatures); i++) + { + g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i); + g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child); + gboolean valid = FALSE; + +#if defined(USE_LIBSODIUM) + valid = crypto_sign_verify_detached (g_bytes_get_data (signature, NULL), + g_bytes_get_data (data, NULL), g_bytes_get_size (data), + pubkey) + == 0; +#elif defined(USE_OPENSSL) + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, pubkey, pubkey_size); + valid = ctx != NULL && pkey != NULL && EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0 + && EVP_DigestVerify (ctx, g_bytes_get_data (signature, NULL), + g_bytes_get_size (signature), g_bytes_get_data (data, NULL), + g_bytes_get_size (data)) + != 0; + if (pkey) + EVP_PKEY_free (pkey); + if (ctx) + EVP_MD_CTX_free (ctx); +#else + errx (EXIT_FAILURE, "Signature validation requested, but support not compiled in"); +#endif + if (valid) + return TRUE; + } + + return FALSE; +} + int main (int argc, char *argv[]) { @@ -167,6 +274,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]; @@ -206,6 +315,7 @@ main (int argc, char *argv[]) OstreeComposefsMode composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE; autofree char *ot_composefs = read_proc_cmdline_key ("ot-composefs"); char *composefs_digest = NULL; + char *composefs_pubkey = NULL; if (ot_composefs) { if (strcmp (ot_composefs, "off") == 0) @@ -214,8 +324,11 @@ main (int argc, char *argv[]) composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE; else if (strcmp (ot_composefs, "on") == 0) composefs_mode = OSTREE_COMPOSEFS_MODE_ON; - else if (strcmp (ot_composefs, "signed") == 0) - composefs_mode = OSTREE_COMPOSEFS_MODE_SIGNED; + else if (strncmp (ot_composefs, "signed=", strlen ("signed=")) == 0) + { + composefs_mode = OSTREE_COMPOSEFS_MODE_SIGNED; + composefs_pubkey = ot_composefs + strlen ("signed="); + } else if (strncmp (ot_composefs, "digest=", strlen ("digest=")) == 0) { composefs_mode = OSTREE_COMPOSEFS_MODE_DIGEST; @@ -266,13 +379,51 @@ main (int argc, char *argv[]) { #ifdef HAVE_COMPOSEFS const char *objdirs[] = { "/sysroot/ostree/repo/objects" }; + g_autofree char *cfs_digest = NULL; struct lcfs_mount_options_s cfs_options = { objdirs, 1, }; if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED) - errx (EXIT_FAILURE, "composefs signature not supported"); + { + g_autoptr (GError) local_error = NULL; + g_autofree char *pubkey = NULL; + gsize pubkey_size; + g_autoptr (GVariant) commit = NULL; + g_autoptr (GVariant) commitmeta = NULL; + + if (!g_file_get_contents (composefs_pubkey, &pubkey, &pubkey_size, &local_error)) + errx (EXIT_FAILURE, "Failed to load public key '%s': %s", composefs_pubkey, + local_error->message); + + if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, + &local_error)) + errx (EXIT_FAILURE, "Error loading signatures from repo: %s", local_error->message); + + g_autoptr (GVariant) signatures + = g_variant_lookup_value (commitmeta, "ostree.sign.ed25519", G_VARIANT_TYPE ("aay")); + if (signatures == NULL) + errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit"); + + g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit); + if (!validate_signature (commit_data, signatures, (guchar *)pubkey, pubkey_size)) + errx (EXIT_FAILURE, "No valid signatures found for public key"); + +#ifdef USE_LIBSYSTEMD + sd_journal_send ("MESSAGE=Validated commit signature using '%s'", composefs_pubkey, NULL); +#endif + + g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0); + g_autoptr (GVariant) cfs_digest_v = g_variant_lookup_value ( + metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING); + if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN) + errx (EXIT_FAILURE, "Signature validation requested, but no valid digest in commit"); + + composefs_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1); + ot_bin2hex (composefs_digest, g_variant_get_data (cfs_digest_v), + g_variant_get_size (cfs_digest_v)); + } cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; @@ -280,7 +431,7 @@ main (int argc, char *argv[]) err (EXIT_FAILURE, "failed to assemble /boot/loader path"); cfs_options.image_mountdir = srcpath; - if (composefs_mode == OSTREE_COMPOSEFS_MODE_DIGEST) + if (composefs_digest != NULL) { cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY; cfs_options.expected_fsverity_digest = composefs_digest; @@ -289,7 +440,7 @@ main (int argc, char *argv[]) #ifdef USE_LIBSYSTEMD if (composefs_mode == OSTREE_COMPOSEFS_MODE_MAYBE) sd_journal_send ("MESSAGE=Trying to mount composefs rootfs", NULL); - else if (composefs_mode == OSTREE_COMPOSEFS_MODE_DIGEST) + else if (composefs_digest != NULL) sd_journal_send ("MESSAGE=Mounting composefs rootfs with expected digest '%s'", composefs_digest, NULL); else