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