symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
# Uncomment this include when adding new development symbols.
-#if BUILDOPT_IS_DEVEL_BUILD
-#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
-#endif
+if BUILDOPT_IS_DEVEL_BUILD
+symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
+endif
# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
wl_versionscript_arg = -Wl,--version-script=
tests/test-remote-add.sh \
tests/test-remote-headers.sh \
tests/test-remote-refs.sh \
+ tests/test-composefs.sh \
tests/test-commit-sign.sh \
tests/test-commit-timestamp.sh \
tests/test-export.sh \
ostree_repo_write_commit_with_time
ostree_repo_read_commit_detached_metadata
ostree_repo_write_commit_detached_metadata
+ostree_repo_commit_add_composefs_metadata
OstreeRepoCheckoutAtOptions
ostree_repo_checkout_at_options_set_devino
OstreeRepoCheckoutMode
- uncomment the include in Makefile-libostree.am
*/
+LIBOSTREE_2023.4 {
+global:
+ ostree_repo_commit_add_composefs_metadata;
+} LIBOSTREE_2023.1;
+
/* Stub section for the stable release *after* this development one; don't
* edit this other than to update the year. This is just a copy/paste
* source. Replace $LASTSTABLE with the last stable version, and $NEWVERSION
add_size_index_to_metadata (self, builder);
- if (!ostree_repo_commit_add_composefs_metadata (self, builder, repo_root, cancellable, error))
- return NULL;
-
return g_variant_ref_sink (g_variant_builder_end (builder));
}
#endif
}
-#ifdef HAVE_COMPOSEFS
-static gboolean
-ostree_repo_commit_add_composefs_sig (OstreeRepo *self, GVariantBuilder *builder,
- guchar *fsverity_digest, GCancellable *cancellable,
- GError **error)
+/**
+ * ostree_repo_commit_add_composefs_metadata:
+ * @self: Repo
+ * @format_version: Must be zero
+ * @dict: A GVariant builder of type a{sv}
+ * @repo_root: the target filesystem tree
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Compute the composefs digest for a filesystem tree
+ * and insert it into metadata for a commit object. The composefs
+ * digest covers the entire filesystem tree and can be verified by
+ * the composefs mount tooling.
+ */
+_OSTREE_PUBLIC
+gboolean
+ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_version,
+ GVariantDict *dict, OstreeRepoFile *repo_root,
+ GCancellable *cancellable, GError **error)
{
- g_autofree char *certfile = NULL;
- g_autofree char *keyfile = NULL;
- g_autoptr (GBytes) sig = NULL;
- guchar digest_digest[LCFS_DIGEST_SIZE];
-
- certfile
- = g_key_file_get_string (self->config, _OSTREE_INTEGRITY_SECTION, "composefs-certfile", NULL);
- keyfile
- = g_key_file_get_string (self->config, _OSTREE_INTEGRITY_SECTION, "composefs-keyfile", NULL);
-
- if (certfile == NULL && keyfile == NULL)
- return TRUE;
-
- if (certfile == NULL)
- return glnx_throw (error, "Error signing compoosefs: keyfile specified but certfile is not");
-
- if (keyfile == NULL)
- return glnx_throw (error, "Error signing compoosefs: certfile specified but keyfile is not");
-
- /* We sign not the fs-verity of the image file itself, but rather we sign a file containing
- * the fs-verity digest. This may seem weird, but disconnecting the signature from the
- * actual image itself has two major advantages:
- * * We can read/mount the image (non-verified) even without the public key in
- * the keyring.
- * * We can apply fs-verity to the image during deploy without the public key in
- * the keyring.
- *
- * This is important because during an update we don't have the public key loaded until
- * we boot into the new initrd.
- */
+#ifdef HAVE_COMPOSEFS
+ /* For now */
+ g_assert (format_version == 0);
- if (lcfs_compute_fsverity_from_data (digest_digest, fsverity_digest, LCFS_DIGEST_SIZE) < 0)
- return glnx_throw_errno (error);
+ /* Create a composefs image and put in deploy dir as .ostree.cfs */
+ g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
- if (!_ostree_fsverity_sign (certfile, keyfile, digest_digest, &sig, cancellable, error))
+ if (!ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
return FALSE;
- g_variant_builder_add (builder, "{sv}", "ostree.composefs-sig", ot_gvariant_new_ay_bytes (sig));
-
- return TRUE;
-}
-#endif
-
-gboolean
-ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, GVariantBuilder *builder,
- OstreeRepoFile *repo_root, GCancellable *cancellable,
- GError **error)
-{
- gboolean add_metadata;
-
- if (!ot_keyfile_get_boolean_with_default (self->config, _OSTREE_INTEGRITY_SECTION,
- "composefs-add-metadata", FALSE, &add_metadata, error))
+ g_autofree guchar *fsverity_digest = NULL;
+ if (!ostree_composefs_target_write (target, -1, &fsverity_digest, cancellable, error))
return FALSE;
- if (add_metadata)
- {
-#ifdef HAVE_COMPOSEFS
- /* Create a composefs image and put in deploy dir as .ostree.cfs */
- g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
-
- if (!ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
- return FALSE;
+ g_variant_dict_insert_value (
+ dict, OSTREE_COMPOSEFS_DIGEST_KEY_V0,
+ ot_gvariant_new_bytearray (fsverity_digest, OSTREE_SHA256_DIGEST_LEN));
- g_autofree guchar *fsverity_digest = NULL;
- if (!ostree_composefs_target_write (target, -1, &fsverity_digest, cancellable, error))
- return FALSE;
-
- g_variant_builder_add (builder, "{sv}", "ostree.composefs",
- ot_gvariant_new_bytearray (fsverity_digest, OSTREE_SHA256_DIGEST_LEN));
-
- if (!ostree_repo_commit_add_composefs_sig (self, builder, fsverity_digest, cancellable,
- error))
- return FALSE;
+ return TRUE;
#else
- return composefs_not_supported (error);
+ return composefs_not_supported (error);
#endif
- }
-
- return TRUE;
}
#define OSTREE_COMMIT_TIMESTAMP "ostree.commit.timestamp"
#define OSTREE_COMMIT_VERSION "ostree.commit.version"
+// The metadata key for composefs
+#define OSTREE_COMPOSEFS_META_PREFIX "ostree.composefs"
+// The fs-verity digest of the composefs, version 0
+#define OSTREE_COMPOSEFS_DIGEST_KEY_V0 OSTREE_COMPOSEFS_META_PREFIX ".digest.v0"
+
#define _OSTREE_INTEGRITY_SECTION "ex-integrity"
typedef enum
gboolean _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GBytes *signature,
GError **error);
-gboolean _ostree_fsverity_sign (const char *certfile, const char *keyfile,
- const guchar *fsverity_digest, GBytes **data_out,
- GCancellable *cancellable, GError **error);
gboolean _ostree_repo_verify_bindings (const char *collection_id, const char *ref_name,
GVariant *commit, GError **error);
OstreeRepoFile *source, GCancellable *cancellable,
GError **error);
-gboolean ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, GVariantBuilder *builder,
- OstreeRepoFile *repo_root,
- GCancellable *cancellable, GError **error);
-
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeComposefsTarget, ostree_composefs_target_unref)
G_END_DECLS
#endif
return TRUE;
}
-
-#if defined(HAVE_OPENSSL)
-static gboolean
-read_pem_x509_certificate (const char *certfile, X509 **cert_ret, GError **error)
-{
- g_autoptr (BIO) bio = NULL;
- X509 *cert;
-
- errno = 0;
- bio = BIO_new_file (certfile, "r");
- if (!bio)
- return glnx_throw_errno_prefix (error, "Error loading composefs certfile '%s'", certfile);
-
- cert = PEM_read_bio_X509 (bio, NULL, NULL, NULL);
- if (!cert)
- return glnx_throw (error, "Error parsing composefs certfile '%s'", certfile);
-
- *cert_ret = cert;
- return TRUE;
-}
-
-static gboolean
-read_pem_pkcs8_private_key (const char *keyfile, EVP_PKEY **pkey_ret, GError **error)
-{
- g_autoptr (BIO) bio;
- EVP_PKEY *pkey;
-
- errno = 0;
- bio = BIO_new_file (keyfile, "r");
- if (!bio)
- return glnx_throw_errno_prefix (error, "Error loading composefs keyfile '%s'", keyfile);
-
- pkey = PEM_read_bio_PrivateKey (bio, NULL, NULL, NULL);
- if (!pkey)
- return glnx_throw (error, "Error parsing composefs keyfile '%s'", keyfile);
-
- *pkey_ret = pkey;
- return TRUE;
-}
-
-static gboolean
-sign_pkcs7 (const void *data_to_sign, size_t data_size, EVP_PKEY *pkey, X509 *cert,
- const EVP_MD *md, BIO **res, GError **error)
-{
- int pkcs7_flags = PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOATTR | PKCS7_NOCERTS | PKCS7_PARTIAL;
- g_autoptr (BIO) bio = NULL;
- g_autoptr (BIO) bio_res = NULL;
- g_autoptr (PKCS7) p7 = NULL;
-
- bio = BIO_new_mem_buf ((void *)data_to_sign, data_size);
- if (!bio)
- return glnx_throw (error, "Can't allocate buffer");
-
- p7 = PKCS7_sign (NULL, NULL, NULL, bio, pkcs7_flags);
- if (!p7)
- return glnx_throw (error, "Can't initialize PKCS#7");
-
- if (!PKCS7_sign_add_signer (p7, cert, pkey, md, pkcs7_flags))
- return glnx_throw (error, "Can't add signer to PKCS#7");
-
- if (PKCS7_final (p7, bio, pkcs7_flags) != 1)
- return glnx_throw (error, "Can't finalize PKCS#7");
-
- bio_res = BIO_new (BIO_s_mem ());
- if (!bio_res)
- return glnx_throw (error, "Can't allocate buffer");
-
- if (i2d_PKCS7_bio (bio_res, p7) != 1)
- return glnx_throw (error, "Can't DER-encode PKCS#7 signature object");
-
- *res = g_steal_pointer (&bio_res);
- return TRUE;
-}
-
-gboolean
-_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
- GBytes **data_out, GCancellable *cancellable, GError **error)
-{
- g_autofree struct fsverity_formatted_digest *d = NULL;
- gsize d_size;
- g_autoptr (X509) cert = NULL;
- g_autoptr (EVP_PKEY) pkey = NULL;
- g_autoptr (BIO) bio_sig = NULL;
- const EVP_MD *md;
- guchar *sig;
- long sig_size;
-
- if (certfile == NULL)
- return glnx_throw (error, "certfile not specified");
-
- if (keyfile == NULL)
- return glnx_throw (error, "keyfile not specified");
-
- if (!read_pem_x509_certificate (certfile, &cert, error))
- return FALSE;
-
- if (!read_pem_pkcs8_private_key (keyfile, &pkey, error))
- return FALSE;
-
- md = EVP_sha256 ();
- if (md == NULL)
- return glnx_throw (error, "No sha256 support in openssl");
-
- d_size = sizeof (struct fsverity_formatted_digest) + OSTREE_SHA256_DIGEST_LEN;
- d = g_malloc0 (d_size);
-
- memcpy (d->magic, "FSVerity", 8);
- d->digest_algorithm = GUINT16_TO_LE (FS_VERITY_HASH_ALG_SHA256);
- d->digest_size = GUINT16_TO_LE (OSTREE_SHA256_DIGEST_LEN);
- memcpy (d->digest, fsverity_digest, OSTREE_SHA256_DIGEST_LEN);
-
- if (!sign_pkcs7 (d, d_size, pkey, cert, md, &bio_sig, error))
- return FALSE;
-
- sig_size = BIO_get_mem_data (bio_sig, &sig);
-
- *data_out = g_bytes_new (sig, sig_size);
-
- return TRUE;
-}
-#else
-gboolean
-_ostree_fsverity_sign (const char *certfile, const char *keyfile, const guchar *fsverity_digest,
- GBytes **data_out, GCancellable *cancellable, GError **error)
-{
- return glnx_throw (error, "fsverity signature support not built");
-}
-#endif
gboolean ostree_repo_write_mtree (OstreeRepo *self, OstreeMutableTree *mtree, GFile **out_file,
GCancellable *cancellable, GError **error);
+_OSTREE_PUBLIC
+gboolean ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_version,
+ GVariantDict *dict, OstreeRepoFile *repo_root,
+ GCancellable *cancellable, GError **error);
+
_OSTREE_PUBLIC
gboolean ostree_repo_write_commit (OstreeRepo *self, const char *parent, const char *subject,
const char *body, GVariant *metadata, OstreeRepoFile *root,
return FALSE;
g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
- g_autoptr (GVariant) metadata_composefs
- = g_variant_lookup_value (metadata, "ostree.composefs", G_VARIANT_TYPE_BYTESTRING);
- g_autoptr (GVariant) metadata_composefs_sig
- = g_variant_lookup_value (metadata, "ostree.composefs-sig", G_VARIANT_TYPE_BYTESTRING);
+ g_autoptr (GVariant) metadata_composefs = g_variant_lookup_value (
+ metadata, OSTREE_COMPOSEFS_DIGEST_KEY_V0, G_VARIANT_TYPE_BYTESTRING);
/* Create a composefs image and put in deploy dir as .ostree.cfs */
g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
if (!_ostree_tmpf_fsverity (repo, &tmpf, NULL, error))
return FALSE;
- if (metadata_composefs && metadata_composefs_sig)
- {
- g_autofree char *composefs_digest_path
- = g_strdup_printf ("%s/.ostree.cfs.digest", checkout_target_name);
- g_autofree char *composefs_sig_path
- = g_strdup_printf ("%s/.ostree.cfs.sig", checkout_target_name);
- g_autoptr (GBytes) digest = g_variant_get_data_as_bytes (metadata_composefs);
- g_autoptr (GBytes) sig = g_variant_get_data_as_bytes (metadata_composefs_sig);
-
- if (!glnx_file_replace_contents_at (osdeploy_dfd, composefs_digest_path,
- g_bytes_get_data (digest, NULL),
- g_bytes_get_size (digest), 0, cancellable, error))
- return FALSE;
-
- if (!glnx_file_replace_contents_at (osdeploy_dfd, composefs_sig_path,
- g_bytes_get_data (sig, NULL), g_bytes_get_size (sig),
- 0, cancellable, error))
- return FALSE;
-
- /* The signature should be applied as a fs-verity signature to the digest file. However
- * we can't do that until boot, because we can't guarantee that the public key is
- * loaded into the keyring until we boot the new initrd. So the signature is applied
- * in ostree-prepare-root on first boot.
- */
- }
-
if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, osdeploy_dfd, composefs_cfs_path,
error))
return FALSE;
static char **opt_key_ids;
static char *opt_sign_name;
static gboolean opt_generate_sizes;
+static gboolean opt_composefs_metadata;
static gboolean opt_disable_fsync;
static char *opt_timestamp;
"Signature type to use (defaults to 'ed25519')", "NAME" },
{ "generate-sizes", 0, 0, G_OPTION_ARG_NONE, &opt_generate_sizes,
"Generate size information along with commit metadata", NULL },
+ { "generate-composefs-metadata", 0, 0, G_OPTION_ARG_NONE, &opt_composefs_metadata,
+ "Generate composefs commit metadata", NULL },
{ "disable-fsync", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_disable_fsync,
"Do not invoke fsync()", NULL },
{ "fsync", 0, 0, G_OPTION_ARG_CALLBACK, parse_fsync_cb, "Specify how to invoke fsync()",
metadata = g_variant_ref_sink (g_variant_dict_end (&bootmeta));
}
+ if (opt_composefs_metadata)
+ {
+ g_autoptr (GVariant) old_metadata = g_steal_pointer (&metadata);
+ g_auto (GVariantDict) newmeta;
+ g_variant_dict_init (&newmeta, old_metadata);
+ if (!ostree_repo_commit_add_composefs_metadata (
+ repo, 0, &newmeta, OSTREE_REPO_FILE (root), cancellable, error))
+ goto out;
+
+ metadata = g_variant_ref_sink (g_variant_dict_end (&newmeta));
+ }
+
if (!opt_timestamp)
{
if (!ostree_repo_write_commit (repo, parent, opt_subject, commit_body, metadata,
return deploy_path;
}
-#ifdef HAVE_COMPOSEFS
-static void
-apply_digest_signature (const char *digestfile, const char *sigfile)
-{
- unsigned char *signature;
- size_t signature_len;
- int digest_is_readonly;
- int digest_fd;
-
- signature = read_file (sigfile, &signature_len);
- if (signature == NULL)
- err (EXIT_FAILURE, "Missing signaure file %s", sigfile);
-
- /* If we're read-only we temporarily make read-write bind mount to sign */
- digest_is_readonly = path_is_on_readonly_fs (digestfile);
- if (digest_is_readonly)
- {
- if (mount (digestfile, digestfile, NULL, MS_BIND | MS_SILENT, NULL) < 0)
- err (EXIT_FAILURE, "failed to bind mount %s (for signing)", digestfile);
- if (mount (digestfile, digestfile, NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
- err (EXIT_FAILURE, "failed to remount %s read-write (for signing)", digestfile);
- }
-
- /* Ensure we re-open after any bindmounts */
- digest_fd = open (digestfile, O_RDONLY | O_CLOEXEC);
- if (digest_fd < 0)
- err (EXIT_FAILURE, "failed to open %s", digestfile);
-
- fsverity_sign (digest_fd, signature, signature_len);
-
- close (digest_fd);
-
- if (digest_is_readonly && umount2 (digestfile, MNT_DETACH) < 0)
- err (EXIT_FAILURE, "failed to unmount %s (after signing)", digestfile);
-
- free (signature);
-
-#ifdef USE_LIBSYSTEMD
- sd_journal_send ("MESSAGE=Applied fsverity signature %s to %s", sigfile, digestfile, NULL);
-#endif
-}
-
-static void
-ensure_digest_fd_is_signed (int digest_fd)
-{
- struct fsverity_read_metadata_arg read_metadata = { 0 };
- char sig_data[1];
- int res;
-
- /* We verify there is a signature by reading one byte of it. */
-
- read_metadata.metadata_type = FS_VERITY_METADATA_TYPE_SIGNATURE;
- read_metadata.offset = 0;
- read_metadata.length = sizeof (sig_data);
- read_metadata.buf_ptr = (size_t)&sig_data;
-
- res = ioctl (digest_fd, FS_IOC_READ_VERITY_METADATA, &read_metadata);
- if (res == -1)
- {
- if (errno == ENODATA)
- err (EXIT_FAILURE, "Digest file is unexpectedly not signed");
- else
- err (EXIT_FAILURE, "Failed to get signature from digest file");
- }
-}
-
-static char *
-read_signed_digest (const char *digestfile, const char *sigfile)
-{
- unsigned fd_flags;
- int digest_fd;
- unsigned char buf[LCFS_DIGEST_SIZE];
- char *digest;
- ssize_t bytes_read;
-
- digest_fd = open (digestfile, O_RDONLY | O_CLOEXEC);
- if (digest_fd < 0)
- err (EXIT_FAILURE, "failed to open %s", digestfile);
-
- /* Check if file is already fsverity */
- if (ioctl (digest_fd, FS_IOC_GETFLAGS, &fd_flags) < 0)
- err (EXIT_FAILURE, "failed to get fd flags for %s", digestfile);
-
- /* If it is not, apply signature */
- if ((fd_flags & FS_VERITY_FL) == 0)
- {
- close (digest_fd);
-
- apply_digest_signature (digestfile, sigfile);
-
- /* Reopen */
- digest_fd = open (digestfile, O_RDONLY | O_CLOEXEC);
- if (digest_fd < 0)
- err (EXIT_FAILURE, "failed to reopen %s", digestfile);
- }
-
- /* By now we know its fs-verify enabled, also ensure it is signed
- * with a key in the keyring */
- ensure_digest_fd_is_signed (digest_fd);
-
- /* Load the expected digest */
- do
- bytes_read = read (digest_fd, buf, LCFS_DIGEST_SIZE);
- while (bytes_read == -1 && errno == EINTR);
- if (bytes_read == -1)
- err (EXIT_FAILURE, "Failed to read digest file");
-
- if (bytes_read != LCFS_DIGEST_SIZE)
- err (EXIT_FAILURE, "Digest file has wrong size");
-
- digest = malloc (LCFS_DIGEST_SIZE * 2 + 1);
- if (digest == NULL)
- err (EXIT_FAILURE, "Out of memory");
-
- bin2hex (digest, buf, LCFS_DIGEST_SIZE);
-
-#ifdef USE_LIBSYSTEMD
- sd_journal_send ("MESSAGE=Signed digest file found for root", NULL);
-#endif
-
- return digest;
-}
-#endif
-
static int
pivot_root (const char *new_root, const char *put_old)
{
};
if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED)
- {
- composefs_digest
- = read_signed_digest (OSTREE_COMPOSEFS_NAME ".digest", OSTREE_COMPOSEFS_NAME ".sig");
- composefs_mode = OSTREE_COMPOSEFS_MODE_DIGEST;
- }
+ errx (EXIT_FAILURE, "composefs signature not supported");
cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
--- /dev/null
+#!/bin/bash
+#
+# 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 <https://www.gnu.org/licenses/>.
+
+set -euo pipefail
+
+if ! ostree --version | grep -q -e '- composefs'; then
+ echo "1..0 #SKIP no composefs support compiled in"
+ exit 0
+fi
+
+. $(dirname $0)/libtest.sh
+
+setup_test_repository "bare-user"
+
+cd ${test_tmpdir}
+$OSTREE checkout test2 test2-co
+$OSTREE commit ${COMMIT_ARGS} -b test-composefs --generate-composefs-metadata test2-co
+orig_composefs_digest=$($OSTREE show --print-metadata-key ostree.composefs.v0 test-composefs)
+assert_streq "${orig_composefs_digest}" '[byte 0x1f, 0x08, 0xe5, 0x8b, 0x14, 0x3b, 0x75, 0x34, 0x76, 0xb5, 0xef, 0x0c, 0x0c, 0x6e, 0xce, 0xbf, 0xde, 0xbb, 0x6d, 0x40, 0x30, 0x5e, 0x35, 0xbd, 0x6f, 0x8e, 0xc1, 0x9c, 0xd0, 0xd1, 0x5b, 0xae]'
+$OSTREE commit ${COMMIT_ARGS} -b test-composefs2 --generate-composefs-metadata test2-co
+new_composefs_digest=$($OSTREE show --print-metadata-key ostree.composefs.v0 test-composefs)
+assert_streq "${orig_composefs_digest}" "${new_composefs_digest}"
+tap_ok "composefs metadata"
+
+tap_end