- `maybe`: Use composefs if supported and there is a composefs image in the deployment directory
- `on`: Require composefs
- `digest=<sha256>`: 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=<path>`: 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
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`
#include <sys/types.h>
#include <unistd.h>
+#include <ostree-core.h>
+#include <ostree-repo-private.h>
+
+#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)
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[])
{
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];
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)
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;
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
{
#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;
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;
#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