diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 8edd7f4de1..1cb7de5477 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -185,12 +185,12 @@ EXTRA_DIST += \ $(top_srcdir)/src/libostree/libostree-released.sym \ $(NULL) -libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/composefs -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \ +libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$(srcdir)/composefs -I$(srcdir)/src/libotutil -I$(srcdir)/src/libotcore -I$(srcdir)/src/libostree -I$(builddir)/src/libostree \ $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_LZMA_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) \ -fvisibility=hidden '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) extern' \ -DPKGLIBEXECDIR=\"$(pkglibexecdir)\" libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions $(addprefix $(wl_versionscript_arg),$(symbol_files)) -libostree_1_la_LIBADD = libotutil.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \ +libostree_1_la_LIBADD = libotutil.la libotcore.la libglnx.la libbsdiff.la $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) \ $(OT_DEP_LZMA_LIBS) $(OT_DEP_ZLIB_LIBS) $(OT_DEP_CRYPTO_LIBS) # Some change between rust-1.21.0-1.fc27 and rust-1.22.1-1.fc27.x86_64 libostree_1_la_LIBADD += $(bupsplitpath) @@ -262,11 +262,6 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-sign-private.h \ $(NULL) -if USE_LIBSODIUM -libostree_1_la_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS) -libostree_1_la_LIBADD += $(OT_DEP_LIBSODIUM_LIBS) -endif # USE_LIBSODIUM - if USE_COMPOSEFS libostree_1_la_LIBADD += libcomposefs.la endif # USE_COMPOSEFS diff --git a/Makefile-ostree.am b/Makefile-ostree.am index db3f7a54be..118db281c7 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -153,8 +153,3 @@ if USE_LIBARCHIVE ostree_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS) ostree_LDADD += $(OT_DEP_LIBARCHIVE_LIBS) endif - -if USE_LIBSODIUM -ostree_CFLAGS += $(OT_DEP_LIBSODIUM_CFLAGS) -ostree_LDADD += $(OT_DEP_LIBSODIUM_LIBS) -endif # USE_LIBSODIUM diff --git a/Makefile-otcore.am b/Makefile-otcore.am new file mode 100644 index 0000000000..8accf85844 --- /dev/null +++ b/Makefile-otcore.am @@ -0,0 +1,24 @@ +# 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, see . + +noinst_LTLIBRARIES += libotcore.la + +libotcore_la_SOURCES = \ + src/libotcore/otcore.h \ + src/libotcore/otcore-ed25519-verify.c \ + $(NULL) + +libotcore_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libotutil -DLOCALEDIR=\"$(datadir)/locale\" $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_INTERNAL_GPGME_CFLAGS) $(OT_DEP_CRYPTO_LIBS) $(LIBSYSTEMD_CFLAGS) +libotcore_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_INTERNAL_GPGME_LIBS) $(LIBSYSTEMD_LIBS) $(OT_DEP_CRYPTO_LIBS) diff --git a/Makefile-switchroot.am b/Makefile-switchroot.am index 09f954f381..6b7cca08de 100644 --- a/Makefile-switchroot.am +++ b/Makefile-switchroot.am @@ -49,10 +49,10 @@ 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/libotcore -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_CPPFLAGS += $(OT_INTERNAL_GIO_UNIX_CFLAGS) $(OT_DEP_CRYPTO_CFLAGS) -I $(srcdir)/libglnx +ostree_prepare_root_LDADD += $(AM_LDFLAGS) $(OT_INTERNAL_GIO_UNIX_LIBS) $(OT_DEP_CRYPTO_LIBS) libotcore.la libotutil.la libglnx.la endif # BUILDOPT_USE_STATIC_COMPILER diff --git a/Makefile.am b/Makefile.am index ca7dec9e4f..19abc0c1e1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -127,6 +127,7 @@ noinst_LTLIBRARIES += libcomposefs.la endif include Makefile-otutil.am +include Makefile-otcore.am include Makefile-libostree.am include Makefile-ostree.am include Makefile-switchroot.am diff --git a/configure.ac b/configure.ac index 5eb7367e3c..02164858bf 100644 --- a/configure.ac +++ b/configure.ac @@ -465,6 +465,10 @@ AS_IF([ test $with_crypto = gnutls ], [ AM_CONDITIONAL(USE_GNUTLS, test $with_crypto = gnutls) dnl end gnutls +dnl we always inject libsodium into our crypto deps in addition to openssl/gnutls +OT_DEP_CRYPTO_CFLAGS="${OT_DEP_CRYPTO_CFLAGS} ${OT_DEP_LIBSODIUM_CFLAGS}" +OT_DEP_CRYPTO_LIBS="${OT_DEP_CRYPTO_LIBS} ${OT_DEP_LIBSODIUM_LIBS}" + dnl Avahi dependency for finding repos AVAHI_DEPENDENCY="avahi-client >= 0.6.31 avahi-glib >= 0.6.31" diff --git a/docs/composefs.md b/docs/composefs.md index 8f63c6e239..8f2c425e96 100644 --- a/docs/composefs.md +++ b/docs/composefs.md @@ -43,7 +43,8 @@ The possible values are: - `maybe`: Use composefs if supported and there is a composefs image in the deployment directory - `on`: Require composefs - `digest=`: Require the mounted composefs image to have a particular digest -- `signed`: This option will be documented in the future; don't use it right now +- `signed=`: Require that the commit is signed as validated by the ed25519 public key specified + by `path` (the path is resolved in the initrd). ### Injecting composefs digests @@ -51,16 +52,37 @@ When generating an OSTree commit, there is a CLI switch `--generate-composefs-me and a corresponding C API `ostree_repo_commit_add_composefs_metadata`. This will inject the composefs digest as metadata into the ostree commit under a metadata key `ostree.composefs.v0`. Because an OSTree commit can be signed, this allows -covering the composefs fsverity digest with a signature. - -At the current time, ostree does not directly support verifying the signature on -the commit object before mounting, but that is in progress. +covering the composefs fsverity digest with a signature. + +### Signatures + +If a commit is signed with a ed25519 private key (see `ostree +--sign`), and `signed=/path/to/public.key` is specified on the +commandline, then the initrd will find the commit being booted in the +system repo and validate its signature against the public key. It will +then ensure that the composefs digest being booted has an fs-verity +digest matching the one in the commit. This allows a fully trusted +read-only /usr. + +The exact usage of the signature is up to the user, but a common way +to use it with transien keys. This is done like this: + * Generate a new keypair before each build + * Embed the public key in the initrd that is part of the commit. + * Ensure the kernel commandline has `ot-signed=/path/to/key` + * After commiting, run `ostree --sign` with the private key. + * Throw away the private key. + +When a transient key is used this way, that ties the initrd with the +userspace part from the commit. This means each initrd can only boot +the very same userspace it was made for. For example, if an older +version of the OS has a security flaw, you can't boot a new fixed +(signed) initrd and have it boot the older userspace with the flaw. ## Requirements -The current default composefs integration in ostree does not have any requirements -from the underlying kernel and filesystem other than having the following -kernel options set: +The current default composefs integration in ostree does not have any +requirements from the underlying kernel and filesystem other than +having the following kernel options set: - `CONFIG_OVERLAY_FS` - `CONFIG_BLK_DEV_LOOP` diff --git a/src/libostree/ostree-sign-ed25519.c b/src/libostree/ostree-sign-ed25519.c index 6ff132600b..21f380aa48 100644 --- a/src/libostree/ostree-sign-ed25519.c +++ b/src/libostree/ostree-sign-ed25519.c @@ -24,31 +24,15 @@ #include "config.h" #include "ostree-sign-ed25519.h" +#include "otcore.h" #include #include -#ifdef HAVE_LIBSODIUM -#include -#define USE_LIBSODIUM -#else - -#if defined(HAVE_OPENSSL) -#include -#define USE_OPENSSL -#endif - -#endif - #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "OSTreeSign" #define OSTREE_SIGN_ED25519_NAME "ed25519" -#define OSTREE_SIGN_METADATA_ED25519_KEY "ostree.sign.ed25519" -#define OSTREE_SIGN_METADATA_ED25519_TYPE "aay" - -#define OSTREE_SIGN_ED25519_SIG_SIZE 64U -#define OSTREE_SIGN_ED25519_PUBKEY_SIZE 32U #define OSTREE_SIGN_ED25519_SEED_SIZE 32U #define OSTREE_SIGN_ED25519_SECKEY_SIZE \ (OSTREE_SIGN_ED25519_SEED_SIZE + OSTREE_SIGN_ED25519_PUBKEY_SIZE) @@ -108,12 +92,11 @@ _ostree_sign_ed25519_init (OstreeSignEd25519 *self) self->public_keys = NULL; self->revoked_keys = NULL; -#if defined(USE_LIBSODIUM) - if (sodium_init () < 0) - self->state = ED25519_FAILED_INITIALIZATION; -#elif defined(USE_OPENSSL) -#else +#if !(defined(USE_OPENSSL) || defined(USE_LIBSODIUM)) self->state = ED25519_NOT_SUPPORTED; +#else + if (!otcore_ed25519_init ()) + self->state = ED25519_FAILED_INITIALIZATION; #endif } @@ -232,7 +215,6 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa { 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 (g_bytes_get_size (signature) != OSTREE_SIGN_ED25519_SIG_SIZE) return glnx_throw ( @@ -246,7 +228,6 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa for (GList *public_key = sign->public_keys; public_key != NULL; public_key = public_key->next) { - /* TODO: use non-list for tons of revoked keys? */ if (g_list_find_custom (sign->revoked_keys, public_key->data, _compare_ed25519_keys) != NULL) @@ -256,32 +237,12 @@ ostree_sign_ed25519_data_verify (OstreeSign *self, GBytes *data, GVariant *signa continue; } -#if defined(USE_LIBSODIUM) - valid = crypto_sign_verify_detached ((guchar *)g_variant_get_data (child), - g_bytes_get_data (data, NULL), - g_bytes_get_size (data), public_key->data) - == 0; -#elif defined(USE_OPENSSL) - EVP_MD_CTX *ctx = EVP_MD_CTX_new (); - if (!ctx) - return glnx_throw (error, "openssl: failed to allocate context"); - EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key->data, - OSTREE_SIGN_ED25519_PUBKEY_SIZE); - if (!pkey) - { - EVP_MD_CTX_free (ctx); - return glnx_throw (error, "openssl: Failed to initialize ed5519 key"); - } - - valid = 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; - - EVP_PKEY_free (pkey); - EVP_MD_CTX_free (ctx); -#endif + bool valid = false; + // Wrap the pubkey in a GBytes as that's what this API wants + g_autoptr (GBytes) public_key_bytes + = g_bytes_new_static (public_key->data, OSTREE_SIGN_ED25519_PUBKEY_SIZE); + if (!otcore_validate_ed25519_signature (data, public_key_bytes, signature, &valid, error)) + return FALSE; if (!valid) { /* Incorrect signature! */ diff --git a/src/libotcore/README.md b/src/libotcore/README.md new file mode 100644 index 0000000000..967a5d9f63 --- /dev/null +++ b/src/libotcore/README.md @@ -0,0 +1 @@ +This library is (will be) shared between `libostree-1.so` and `ostree-prepare-root`. diff --git a/src/libotcore/otcore-ed25519-verify.c b/src/libotcore/otcore-ed25519-verify.c new file mode 100644 index 0000000000..1c0ec2b83e --- /dev/null +++ b/src/libotcore/otcore-ed25519-verify.c @@ -0,0 +1,115 @@ +/* + * 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, see . + */ + +#include "config.h" + +#include "otcore.h" + +/* Initialize global state; may be called multiple times and is idempotent. */ +bool +otcore_ed25519_init (void) +{ +#if defined(HAVE_LIBSODIUM) + static gssize initstate; + if (g_once_init_enter (&initstate)) + { + int val = sodium_init () >= 0 ? 1 : -1; + g_once_init_leave (&initstate, val); + } + switch (initstate) + { + case 1: + return true; + case -1: + return false; + default: + g_assert_not_reached (); + } +#else + return true; +#endif +} + +/* Validate a single ed25519 signature. If there is an unexpected state, such + * as an ill-forumed public key or signature, a hard error will be returned. + * + * If the signature is not correct, this function will return successfully, but + * `out_valid` will be set to `false`. + * + * If the signature is correct, `out_valid` will be `true`. + * */ +gboolean +otcore_validate_ed25519_signature (GBytes *data, GBytes *public_key, GBytes *signature, + bool *out_valid, GError **error) +{ + // Since this is signature verification code, let's verify preconditions. + g_assert (data); + g_assert (public_key); + g_assert (signature); + g_assert (out_valid); + // It is OK for error to be NULL, though according to GError rules. + +#if defined(HAVE_LIBSODIUM) || defined(HAVE_OPENSSL) + // And strictly verify pubkey and signature lengths + if (g_bytes_get_size (public_key) != OSTREE_SIGN_ED25519_PUBKEY_SIZE) + return glnx_throw (error, "Invalid public key of %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, + (gsize)g_bytes_get_size (public_key), + (gsize)OSTREE_SIGN_ED25519_PUBKEY_SIZE); + const guint8 *public_key_buf = g_bytes_get_data (public_key, NULL); + if (g_bytes_get_size (signature) != OSTREE_SIGN_ED25519_SIG_SIZE) + return glnx_throw ( + error, "Invalid signature length of %" G_GSIZE_FORMAT " bytes, expected %" G_GSIZE_FORMAT, + (gsize)g_bytes_get_size (signature), (gsize)OSTREE_SIGN_ED25519_SIG_SIZE); + const guint8 *signature_buf = g_bytes_get_data (signature, NULL); + +#endif + +#if defined(HAVE_LIBSODIUM) + // Note that libsodium assumes the passed byte arrays for the signature and public key + // have at least the expected length, but we checked that above. + if (crypto_sign_verify_detached (signature_buf, g_bytes_get_data (data, NULL), + g_bytes_get_size (data), public_key_buf) + == 0) + { + *out_valid = true; + } + return TRUE; +#elif defined(HAVE_OPENSSL) + EVP_MD_CTX *ctx = EVP_MD_CTX_new (); + if (!ctx) + return glnx_throw (error, "openssl: failed to allocate context"); + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key (EVP_PKEY_ED25519, NULL, public_key_buf, + OSTREE_SIGN_ED25519_PUBKEY_SIZE); + if (!pkey) + { + EVP_MD_CTX_free (ctx); + return glnx_throw (error, "openssl: Failed to initialize ed5519 key"); + } + if (EVP_DigestVerifyInit (ctx, NULL, NULL, NULL, pkey) != 0 + && EVP_DigestVerify (ctx, signature_buf, OSTREE_SIGN_ED25519_SIG_SIZE, + g_bytes_get_data (data, NULL), g_bytes_get_size (data)) + != 0) + { + *out_valid = true; + } + EVP_PKEY_free (pkey); + EVP_MD_CTX_free (ctx); + return TRUE; +#else + return glnx_throw (error, "ed25519 signature validation requested, but support not compiled in"); +#endif +} diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h new file mode 100644 index 0000000000..fdbca4932c --- /dev/null +++ b/src/libotcore/otcore.h @@ -0,0 +1,44 @@ +/* + * 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, see . + */ + +#pragma once + +#include "config.h" + +#include "otutil.h" +#include + +#ifdef HAVE_LIBSODIUM +#include +#define USE_LIBSODIUM +#elif defined(HAVE_OPENSSL) +#include +#define USE_OPENSSL +#endif + +// Length of a signature in bytes +#define OSTREE_SIGN_ED25519_SIG_SIZE 64U +// Length of a public key in bytes +#define OSTREE_SIGN_ED25519_PUBKEY_SIZE 32U +// This key is stored inside commit metadata. +#define OSTREE_SIGN_METADATA_ED25519_KEY "ostree.sign.ed25519" +// The variant type +#define OSTREE_SIGN_METADATA_ED25519_TYPE "aay" + +bool otcore_ed25519_init (void); +gboolean otcore_validate_ed25519_signature (GBytes *data, GBytes *pubkey, GBytes *signature, + bool *out_valid, GError **error); diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index ab8bb14227..ea07331bfc 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -70,6 +70,11 @@ #include #include +#include +#include + +#include "otcore.h" + /* 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 +165,80 @@ pivot_root (const char *new_root, const char *put_old) return syscall (__NR_pivot_root, new_root, put_old); } +#ifdef HAVE_COMPOSEFS +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) +{ + g_autoptr (GBytes) pubkey_buf = g_bytes_new_static (pubkey, pubkey_size); + + for (gsize i = 0; i < g_variant_n_children (signatures); i++) + { + g_autoptr (GError) local_error = NULL; + g_autoptr (GVariant) child = g_variant_get_child_value (signatures, i); + g_autoptr (GBytes) signature = g_variant_get_data_as_bytes (child); + bool valid = false; + + if (!otcore_validate_ed25519_signature (data, pubkey_buf, signature, &valid, &local_error)) + errx (EXIT_FAILURE, "signature verification failed: %s", local_error->message); + if (valid) + return TRUE; + } + + return FALSE; +} +#endif + int main (int argc, char *argv[]) { @@ -167,6 +246,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 +287,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 +296,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; @@ -229,6 +314,7 @@ main (int argc, char *argv[]) if (composefs_mode == OSTREE_COMPOSEFS_MODE_MAYBE) composefs_mode = OSTREE_COMPOSEFS_MODE_OFF; (void)composefs_digest; + (void)composefs_pubkey; #endif /* Query the repository configuration - this is an operating system builder @@ -266,13 +352,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 +404,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 +413,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