mirror of
https://github.com/ostreedev/ostree.git
synced 2024-12-22 17:35:55 +03:00
lib: Rework composefs metadata, drop custom signatures
We will be switching to handling signature verification of the target ostree commit.
This commit is contained in:
parent
1fe2bb9f5a
commit
a6f2d053c8
@ -174,9 +174,9 @@ endif # USE_GPGME
|
||||
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=
|
||||
|
@ -72,6 +72,7 @@ _installed_or_uninstalled_test_scripts = \
|
||||
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 \
|
||||
|
@ -426,6 +426,7 @@ ostree_repo_write_commit
|
||||
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
|
||||
|
@ -20,6 +20,11 @@
|
||||
- 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
|
||||
|
@ -2916,9 +2916,6 @@ add_auto_metadata (OstreeRepo *self, GVariant *original_metadata, OstreeRepoFile
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -566,89 +566,46 @@ ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget *target,
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_COMPOSEFS
|
||||
static gboolean
|
||||
ostree_repo_commit_add_composefs_sig (OstreeRepo *self, GVariantBuilder *builder,
|
||||
guchar *fsverity_digest, 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.
|
||||
*/
|
||||
|
||||
if (lcfs_compute_fsverity_from_data (digest_digest, fsverity_digest, LCFS_DIGEST_SIZE) < 0)
|
||||
return glnx_throw_errno (error);
|
||||
|
||||
if (!_ostree_fsverity_sign (certfile, keyfile, digest_digest, &sig, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
g_variant_builder_add (builder, "{sv}", "ostree.composefs-sig", ot_gvariant_new_ay_bytes (sig));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 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, GVariantBuilder *builder,
|
||||
OstreeRepoFile *repo_root, GCancellable *cancellable,
|
||||
GError **error)
|
||||
ostree_repo_commit_add_composefs_metadata (OstreeRepo *self, guint format_version,
|
||||
GVariantDict *dict, OstreeRepoFile *repo_root,
|
||||
GCancellable *cancellable, GError **error)
|
||||
{
|
||||
gboolean add_metadata;
|
||||
#ifdef HAVE_COMPOSEFS
|
||||
/* For now */
|
||||
g_assert (format_version == 0);
|
||||
|
||||
if (!ot_keyfile_get_boolean_with_default (self->config, _OSTREE_INTEGRITY_SECTION,
|
||||
"composefs-add-metadata", FALSE, &add_metadata, error))
|
||||
/* 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;
|
||||
|
||||
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 ();
|
||||
g_autofree guchar *fsverity_digest = NULL;
|
||||
if (!ostree_composefs_target_write (target, -1, &fsverity_digest, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!ostree_repo_checkout_composefs (self, target, repo_root, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
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;
|
||||
#else
|
||||
return composefs_not_supported (error);
|
||||
#endif
|
||||
}
|
||||
g_variant_dict_insert_value (
|
||||
dict, OSTREE_COMPOSEFS_DIGEST_KEY_V0,
|
||||
ot_gvariant_new_bytearray (fsverity_digest, OSTREE_SHA256_DIGEST_LEN));
|
||||
|
||||
return TRUE;
|
||||
#else
|
||||
return composefs_not_supported (error);
|
||||
#endif
|
||||
}
|
||||
|
@ -65,6 +65,11 @@ G_BEGIN_DECLS
|
||||
#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
|
||||
@ -399,9 +404,6 @@ gboolean _ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf, _OstreeFeatureSupport fs
|
||||
|
||||
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);
|
||||
@ -465,10 +467,6 @@ gboolean ostree_repo_checkout_composefs (OstreeRepo *self, OstreeComposefsTarget
|
||||
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
|
||||
|
@ -206,131 +206,3 @@ _ostree_tmpf_fsverity (OstreeRepo *self, GLnxTmpfile *tmpf, GBytes *signature, G
|
||||
#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
|
||||
|
@ -690,6 +690,11 @@ _OSTREE_PUBLIC
|
||||
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,
|
||||
|
@ -659,10 +659,8 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
|
||||
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 ();
|
||||
@ -695,32 +693,6 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
|
||||
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;
|
||||
|
@ -70,6 +70,7 @@ static char *opt_gpg_homedir;
|
||||
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;
|
||||
|
||||
@ -161,6 +162,8 @@ static GOptionEntry options[] = {
|
||||
"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()",
|
||||
@ -872,6 +875,18 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
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,
|
||||
|
@ -181,130 +181,6 @@ resolve_deploy_path (const char *root_mountpoint)
|
||||
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)
|
||||
{
|
||||
@ -439,11 +315,7 @@ main (int argc, char *argv[])
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
|
39
tests/test-composefs.sh
Executable file
39
tests/test-composefs.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/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
|
Loading…
Reference in New Issue
Block a user