Skip to content

Commit

Permalink
ostree-prepare-root: Validate signatures when requested
Browse files Browse the repository at this point in the history
If requested, by specifying ot-composefs=signed=/path/to/pub.key then
the commit object is validated against the specified ed25519 public
key, and if valid, the composefs digest from the commit object is used
to ensure we boot the right digest.
  • Loading branch information
alexlarsson committed Jul 8, 2023
1 parent a6d9c71 commit 1483595
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 7 deletions.
14 changes: 12 additions & 2 deletions Makefile-switchroot.am
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
161 changes: 156 additions & 5 deletions src/switchroot/ostree-prepare-root.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@
#include <sys/types.h>
#include <unistd.h>

#include <ostree-core.h>
#include <ostree-repo-private.h>

#ifdef HAVE_LIBSODIUM
#include <sodium.h>
#define USE_LIBSODIUM
#else

#if defined(HAVE_OPENSSL)
#include <openssl/evp.h>
#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)
Expand Down Expand Up @@ -160,13 +175,107 @@ 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[])
{
char srcpath[PATH_MAX];

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];
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -266,21 +379,59 @@ 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;

if (snprintf (srcpath, sizeof (srcpath), "%s/.ostree.mnt", deploy_path) < 0)
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;
Expand All @@ -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
Expand Down

0 comments on commit 1483595

Please sign in to comment.