mirror of
https://github.com/ostreedev/ostree.git
synced 2024-12-26 03:22:08 +03:00
Merge pull request #2879 from alexlarsson/composefs-new-signature-approach
composefs: Change how we do signatures
This commit is contained in:
commit
c4591c2d28
@ -1 +1 @@
|
||||
Subproject commit e5ab5e2dc6aa6bd2daab3052553b787efe16fc7d
|
||||
Subproject commit af8e1a7cf6864c27e2ceac44cc145bd78734df30
|
@ -575,6 +575,7 @@ ostree_repo_commit_add_composefs_sig (OstreeRepo *self, GVariantBuilder *builder
|
||||
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);
|
||||
@ -590,7 +591,22 @@ ostree_repo_commit_add_composefs_sig (OstreeRepo *self, GVariantBuilder *builder
|
||||
if (keyfile == NULL)
|
||||
return glnx_throw (error, "Error signing compoosefs: certfile specified but keyfile is not");
|
||||
|
||||
if (!_ostree_fsverity_sign (certfile, keyfile, fsverity_digest, &sig, cancellable, error))
|
||||
/* 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));
|
||||
|
@ -649,7 +649,6 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
|
||||
#ifdef HAVE_COMPOSEFS
|
||||
if (repo->composefs_wanted != OT_TRISTATE_NO)
|
||||
{
|
||||
gboolean apply_composefs_signature;
|
||||
g_autofree guchar *fsverity_digest = NULL;
|
||||
g_auto (GLnxTmpfile) tmpf = {
|
||||
0,
|
||||
@ -659,11 +658,6 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
|
||||
if (!ostree_repo_load_commit (repo, revision, &commit_variant, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (!ot_keyfile_get_boolean_with_default (repo->config, _OSTREE_INTEGRITY_SECTION,
|
||||
"composefs-apply-sig", TRUE,
|
||||
&apply_composefs_signature, error))
|
||||
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);
|
||||
@ -698,25 +692,33 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
|
||||
if (!glnx_fchmod (tmpf.fd, 0644, error))
|
||||
return FALSE;
|
||||
|
||||
if (metadata_composefs_sig && apply_composefs_signature)
|
||||
{
|
||||
/* We can't apply the signature during deploy, because the corresponding public key for
|
||||
this commit is not loaded into the keyring. So, we delay fs-verity application to the
|
||||
first boot. */
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_ostree_tmpf_fsverity (repo, &tmpf, NULL, 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,
|
||||
|
@ -237,4 +237,19 @@ fsverity_sign (int fd, unsigned char *signature, size_t signature_len)
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void
|
||||
bin2hex (char *out_buf, const unsigned char *inbuf, size_t len)
|
||||
{
|
||||
static const char hexchars[] = "0123456789abcdef";
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0, j = 0; i < len; i++, j += 2)
|
||||
{
|
||||
unsigned char byte = inbuf[i];
|
||||
out_buf[j] = hexchars[byte >> 4];
|
||||
out_buf[j + 1] = hexchars[byte & 0xF];
|
||||
}
|
||||
out_buf[j] = '\0';
|
||||
}
|
||||
|
||||
#endif /* __OSTREE_MOUNT_UTIL_H_ */
|
||||
|
@ -96,6 +96,7 @@
|
||||
|
||||
#ifdef HAVE_COMPOSEFS
|
||||
#include <libcomposefs/lcfs-mount.h>
|
||||
#include <libcomposefs/lcfs-writer.h>
|
||||
#endif
|
||||
|
||||
#include "ostree-mount-util.h"
|
||||
@ -180,6 +181,130 @@ 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)
|
||||
{
|
||||
@ -312,57 +437,12 @@ main (int argc, char *argv[])
|
||||
objdirs,
|
||||
1,
|
||||
};
|
||||
int cfs_fd;
|
||||
unsigned cfs_flags;
|
||||
|
||||
cfs_fd = open (OSTREE_COMPOSEFS_NAME, O_RDONLY | O_CLOEXEC);
|
||||
if (cfs_fd < 0)
|
||||
if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
goto nocfs;
|
||||
|
||||
err (EXIT_FAILURE, "failed to open %s", OSTREE_COMPOSEFS_NAME);
|
||||
}
|
||||
|
||||
/* Check if file is already fsverity */
|
||||
if (ioctl (cfs_fd, FS_IOC_GETFLAGS, &cfs_flags) < 0)
|
||||
err (EXIT_FAILURE, "failed to get %s flags", OSTREE_COMPOSEFS_NAME);
|
||||
|
||||
/* It is not, apply signature (if it exists) */
|
||||
if ((cfs_flags & FS_VERITY_FL) == 0)
|
||||
{
|
||||
const char signame[] = OSTREE_COMPOSEFS_NAME ".sig";
|
||||
unsigned char *signature;
|
||||
size_t signature_len;
|
||||
|
||||
signature = read_file (signame, &signature_len);
|
||||
if (signature != NULL)
|
||||
{
|
||||
/* If we're read-only we temporarily make it read-write to sign the image */
|
||||
if (!sysroot_currently_writable
|
||||
&& mount (root_mountpoint, root_mountpoint, NULL, MS_REMOUNT | MS_SILENT, NULL)
|
||||
< 0)
|
||||
err (EXIT_FAILURE, "failed to remount rootfs writable (for signing)");
|
||||
|
||||
fsverity_sign (cfs_fd, signature, signature_len);
|
||||
free (signature);
|
||||
|
||||
if (!sysroot_currently_writable
|
||||
&& mount (root_mountpoint, root_mountpoint, NULL,
|
||||
MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL)
|
||||
< 0)
|
||||
err (EXIT_FAILURE, "failed to remount rootfs back read-only (after signing)");
|
||||
|
||||
#ifdef USE_LIBSYSTEMD
|
||||
sd_journal_send ("MESSAGE=Applied fsverity signature %s", signame, NULL);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef USE_LIBSYSTEMD
|
||||
sd_journal_send ("MESSAGE=No fsverity signature found for root", NULL);
|
||||
#endif
|
||||
}
|
||||
composefs_digest
|
||||
= read_signed_digest (OSTREE_COMPOSEFS_NAME ".digest", OSTREE_COMPOSEFS_NAME ".sig");
|
||||
composefs_mode = OSTREE_COMPOSEFS_MODE_DIGEST;
|
||||
}
|
||||
|
||||
cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY;
|
||||
@ -371,17 +451,23 @@ 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_SIGNED)
|
||||
{
|
||||
cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_SIGNATURE | LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
|
||||
}
|
||||
else if (composefs_mode == OSTREE_COMPOSEFS_MODE_DIGEST)
|
||||
if (composefs_mode == OSTREE_COMPOSEFS_MODE_DIGEST)
|
||||
{
|
||||
cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY;
|
||||
cfs_options.expected_digest = composefs_digest;
|
||||
}
|
||||
|
||||
if (lcfs_mount_fd (cfs_fd, TMP_SYSROOT, &cfs_options) == 0)
|
||||
#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)
|
||||
sd_journal_send ("MESSAGE=Mounting composefs rootfs with expected digest '%s'",
|
||||
composefs_digest, NULL);
|
||||
else
|
||||
sd_journal_send ("MESSAGE=Mounting composefs rootfs", NULL);
|
||||
#endif
|
||||
|
||||
if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, TMP_SYSROOT, &cfs_options) == 0)
|
||||
{
|
||||
int fd = open (_OSTREE_COMPOSEFS_ROOT_STAMP, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
|
||||
if (fd < 0)
|
||||
@ -390,10 +476,19 @@ main (int argc, char *argv[])
|
||||
|
||||
using_composefs = 1;
|
||||
}
|
||||
|
||||
close (cfs_fd);
|
||||
|
||||
nocfs:
|
||||
else
|
||||
{
|
||||
#ifdef USE_LIBSYSTEMD
|
||||
if (errno == ENOVERITY)
|
||||
sd_journal_send ("MESSAGE=No verity in composefs image", NULL);
|
||||
else if (errno == EWRONGVERITY)
|
||||
sd_journal_send ("MESSAGE=Wrong verity digest in composefs image", NULL);
|
||||
else if (errno == ENOSIGNATURE)
|
||||
sd_journal_send ("MESSAGE=Missing signature in composefs image", NULL);
|
||||
else
|
||||
sd_journal_send ("MESSAGE=Mounting composefs image failed: %s", strerror (errno), NULL);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
err (EXIT_FAILURE, "Composefs not supported");
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user