New upstream version 2020.4
This commit is contained in:
commit
10a6e008df
129 files changed
+7441
-1244
No files matched your search
@@ -26,6 +26,16 @@ if ! test -d /ostree/repo; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Gracefully exit if the grub2 configuration has BLS enabled,
|
||||
# and the installed version has support for the blscfg module.
|
||||
# Since there is no need to create menu entries for that case.
|
||||
# See: https://src.fedoraproject.org/rpms/grub2/c/7c2bab5e98d
|
||||
. /etc/default/grub
|
||||
if test -f /boot/grub2/.grub2-blscfg-supported && \
|
||||
test "${GRUB_ENABLE_BLSCFG}" = "true"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Make sure we're in the right environment
|
||||
if ! test -n "${GRUB_DEVICE}"; then
|
||||
echo "This script must be run as a child of grub2-mkconfig" 1>&2
|
||||
|
||||
@@ -23,7 +23,7 @@ Documentation=man:ostree(1)
|
||||
ConditionPathExists=/run/ostree-booted
|
||||
DefaultDependencies=no
|
||||
|
||||
RequiresMountsFor=/sysroot
|
||||
RequiresMountsFor=/sysroot /boot
|
||||
After=local-fs.target
|
||||
Before=basic.target final.target
|
||||
# We want to make sure the transaction logs are persisted to disk:
|
||||
@@ -39,3 +39,11 @@ ExecStop=/usr/bin/ostree admin finalize-staged
|
||||
# here is that people don't get an upgrade. We need to handle
|
||||
# cases with slow rotational media, etc.
|
||||
TimeoutStopSec=5m
|
||||
# OSTree should never touch /var at all...except, we need to remove
|
||||
# the /var/.updated flag, so we can't just `InaccessiblePaths=/var` right now.
|
||||
# For now, let's at least use ProtectHome just so we have some sandboxing
|
||||
# of that.
|
||||
ProtectHome=yes
|
||||
# And we shouldn't affect the current deployment's /etc.
|
||||
ReadOnlyPaths=/etc
|
||||
# We write to /sysroot and /boot of course.
|
||||
@@ -23,6 +23,7 @@ ConditionKernelCommandLine=ostree
|
||||
ConditionPathExists=/etc/initrd-release
|
||||
OnFailure=emergency.target
|
||||
After=sysroot.mount
|
||||
Requires=sysroot.mount
|
||||
Before=initrd-root-fs.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -17,12 +17,6 @@
|
||||
Boston, MA 02111-1307, USA.
|
||||
***/
|
||||
|
||||
/* Add new symbols here. Release commits should copy this section into -released.sym. */
|
||||
LIBOSTREE_2020.2 {
|
||||
global:
|
||||
someostree_symbol_deleteme;
|
||||
} LIBOSTREE_2020.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
|
||||
|
||||
@@ -593,6 +593,28 @@ global:
|
||||
ostree_sysroot_set_mount_namespace_in_use;
|
||||
} LIBOSTREE_2019.6;
|
||||
|
||||
/* Add new symbols here. Release commits should copy this section into -released.sym. */
|
||||
LIBOSTREE_2020.4 {
|
||||
global:
|
||||
ostree_repo_commit_modifier_set_sepolicy_from_commit;
|
||||
ostree_sign_get_type;
|
||||
ostree_sign_get_all;
|
||||
ostree_sign_commit;
|
||||
ostree_sign_commit_verify;
|
||||
ostree_sign_data;
|
||||
ostree_sign_data_verify;
|
||||
ostree_sign_get_by_name;
|
||||
ostree_sign_get_name;
|
||||
ostree_sign_clear_keys;
|
||||
ostree_sign_load_pk;
|
||||
ostree_sign_metadata_format;
|
||||
ostree_sign_metadata_key;
|
||||
ostree_sign_set_pk;
|
||||
ostree_sign_add_pk;
|
||||
ostree_sign_set_sk;
|
||||
ostree_sign_summary;
|
||||
} LIBOSTREE_2020.1;
|
||||
|
||||
/* No new symbols in 2020.2 */
|
||||
|
||||
/* NOTE: Only add more content here in release commits! See the
|
||||
|
||||
@@ -73,6 +73,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderOverride, g_object_unref)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free)
|
||||
G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL)
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSign, g_object_unref)
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
@@ -144,6 +144,10 @@ create_config_from_boot_loader_entries (OstreeBootloaderUboot *self,
|
||||
if (val)
|
||||
g_ptr_array_add (new_lines, g_strdup_printf ("fdt_file%s=%s", index_suffix, val));
|
||||
|
||||
val = ostree_bootconfig_parser_get (config, "fdtdir");
|
||||
if (val)
|
||||
g_ptr_array_add (new_lines, g_strdup_printf ("fdtdir%s=%s", index_suffix, val));
|
||||
|
||||
val = ostree_bootconfig_parser_get (config, "options");
|
||||
if (val)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "ostree-cmdprivate.h"
|
||||
#include "ostree-repo-private.h"
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-repo-pull-private.h"
|
||||
#include "ostree-repo-static-delta-private.h"
|
||||
#include "ostree-sysroot-private.h"
|
||||
#include "ostree-bootloader-grub2.h"
|
||||
|
||||
@@ -2376,6 +2376,13 @@ ostree_commit_get_parent (GVariant *commit_variant)
|
||||
return ostree_checksum_from_bytes_v (bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_commit_get_timestamp:
|
||||
* @commit_variant: Commit object
|
||||
*
|
||||
* Returns: timestamp in seconds since the Unix epoch, UTC
|
||||
* Since: 2016.3
|
||||
*/
|
||||
guint64
|
||||
ostree_commit_get_timestamp (GVariant *commit_variant)
|
||||
{
|
||||
|
||||
@@ -212,6 +212,19 @@ typedef enum {
|
||||
* Since: 2014.9
|
||||
*/
|
||||
#define OSTREE_COMMIT_META_KEY_VERSION "version"
|
||||
|
||||
/**
|
||||
* OSTREE_COMMIT_META_KEY_ARCHITECTURE:
|
||||
*
|
||||
* GVariant type `s`. Intended to describe the CPU architecture. This is a freeform string, and some distributions
|
||||
* which have existing package managers might want to match that schema. If you
|
||||
* don't have a prior schema, it's recommended to use `uname -m` by default (i.e. the Linux kernel schema). In the future
|
||||
* ostree might include a builtin function to compare architectures.
|
||||
*
|
||||
* Since: 2020.4
|
||||
*/
|
||||
#define OSTREE_COMMIT_META_KEY_ARCHITECTURE "ostree.architecture"
|
||||
|
||||
/**
|
||||
* OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE:
|
||||
*
|
||||
|
||||
@@ -202,6 +202,7 @@ _ostree_fetcher_should_retry_request (const GError *error,
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_NOT_FOUND) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE) ||
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT) ||
|
||||
#if !GLIB_CHECK_VERSION(2, 44, 0)
|
||||
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE) ||
|
||||
#else
|
||||
|
||||
@@ -134,7 +134,7 @@ GVariant * ostree_gpg_verify_result_get_all (OstreeGpgVerifyResult *result,
|
||||
* for future variations.
|
||||
**/
|
||||
typedef enum {
|
||||
OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT = 0
|
||||
OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT = (0 << 0),
|
||||
} OstreeGpgSignatureFormatFlags;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
|
||||
@@ -51,15 +51,33 @@
|
||||
#define FICLONE _IOW(0x94, 9, int)
|
||||
#endif
|
||||
|
||||
|
||||
/* If fsync is enabled and we're in a txn, we write into a staging dir for
|
||||
* commit, but we also allow direct writes into objects/ for e.g. hardlink
|
||||
* imports.
|
||||
/* Understanding ostree's fsync strategy
|
||||
*
|
||||
* A long time ago, ostree used to invoke fsync() on each object,
|
||||
* then move it into the objects directory. However, it turned
|
||||
* out to be a *lot* faster to write the objects into a separate "staging"
|
||||
* directory (letting the filesystem handle writeback how it likes)
|
||||
* and then only walk over each of the files, fsync(), then rename()
|
||||
* into place. See also https://lwn.net/Articles/789024/
|
||||
*
|
||||
* (We also support a "disable fsync entirely" mode, where you don't
|
||||
* care about integrity; e.g. test suites using disposable VMs).
|
||||
*
|
||||
* This "delayed fsync" pattern though is much worse for other concurrent processes
|
||||
* like databases because it forces a lot to go through the filesystem
|
||||
* journal at once once we do the sync. So now we support a `per_object_fsync`
|
||||
* option that again invokes `fsync()` directly. This also notably
|
||||
* provides "backpressure", ensuring we aren't queuing up a huge amount
|
||||
* of I/O at once.
|
||||
*/
|
||||
|
||||
/* The directory where we place content */
|
||||
static int
|
||||
commit_dest_dfd (OstreeRepo *self)
|
||||
{
|
||||
if (self->in_transaction && !self->disable_fsync)
|
||||
if (self->per_object_fsync)
|
||||
return self->objects_dir_fd;
|
||||
else if (self->in_transaction && !self->disable_fsync)
|
||||
return self->commit_stagedir.fd;
|
||||
else
|
||||
return self->objects_dir_fd;
|
||||
@@ -420,7 +438,7 @@ commit_loose_regfile_object (OstreeRepo *self,
|
||||
/* Ensure that in case of a power cut, these files have the data we
|
||||
* want. See http://lwn.net/Articles/322823/
|
||||
*/
|
||||
if (!self->in_transaction && !self->disable_fsync)
|
||||
if (!self->disable_fsync && self->per_object_fsync)
|
||||
{
|
||||
if (fsync (tmpf->fd) == -1)
|
||||
return glnx_throw_errno_prefix (error, "fsync");
|
||||
@@ -1835,6 +1853,52 @@ ostree_repo_prepare_transaction (OstreeRepo *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Synchronize the directories holding the objects */
|
||||
static gboolean
|
||||
fsync_object_dirs (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("fsync objdirs", error);
|
||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||
|
||||
if (self->disable_fsync)
|
||||
return TRUE; /* No fsync? Nothing to do then. */
|
||||
|
||||
if (!glnx_dirfd_iterator_init_at (self->objects_dir_fd, ".", FALSE, &dfd_iter, error))
|
||||
return FALSE;
|
||||
while (TRUE)
|
||||
{
|
||||
struct dirent *dent;
|
||||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
||||
return FALSE;
|
||||
if (dent == NULL)
|
||||
break;
|
||||
if (dent->d_type != DT_DIR)
|
||||
continue;
|
||||
/* All object directories only have two character entries */
|
||||
if (strlen (dent->d_name) != 2)
|
||||
continue;
|
||||
|
||||
glnx_autofd int target_dir_fd = -1;
|
||||
if (!glnx_opendirat (self->objects_dir_fd, dent->d_name, FALSE,
|
||||
&target_dir_fd, error))
|
||||
return FALSE;
|
||||
/* This synchronizes the directory to ensure all the objects we wrote
|
||||
* are there. We need to do this before removing the .commitpartial
|
||||
* stamp (or have a ref point to the commit).
|
||||
*/
|
||||
if (fsync (target_dir_fd) == -1)
|
||||
return glnx_throw_errno_prefix (error, "fsync");
|
||||
}
|
||||
|
||||
/* In case we created any loose object subdirs, make sure they are on disk */
|
||||
if (fsync (self->objects_dir_fd) == -1)
|
||||
return glnx_throw_errno_prefix (error, "fsync");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Called for commit, to iterate over the "staging" directory and rename all the
|
||||
* objects into the primary objects/ location. Notably this is called only after
|
||||
* syncfs() has potentially been invoked to ensure that all objects have been
|
||||
@@ -1856,10 +1920,6 @@ rename_pending_loose_objects (OstreeRepo *self,
|
||||
while (TRUE)
|
||||
{
|
||||
struct dirent *dent;
|
||||
gboolean renamed_some_object = FALSE;
|
||||
g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
|
||||
char loose_objpath[_OSTREE_LOOSE_PATH_MAX];
|
||||
|
||||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
||||
return FALSE;
|
||||
if (dent == NULL)
|
||||
@@ -1872,10 +1932,12 @@ rename_pending_loose_objects (OstreeRepo *self,
|
||||
if (strlen (dent->d_name) != 2)
|
||||
continue;
|
||||
|
||||
g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, };
|
||||
if (!glnx_dirfd_iterator_init_at (dfd_iter.fd, dent->d_name, FALSE,
|
||||
&child_dfd_iter, error))
|
||||
return FALSE;
|
||||
|
||||
char loose_objpath[_OSTREE_LOOSE_PATH_MAX];
|
||||
loose_objpath[0] = dent->d_name[0];
|
||||
loose_objpath[1] = dent->d_name[1];
|
||||
loose_objpath[2] = '/';
|
||||
@@ -1899,41 +1961,13 @@ rename_pending_loose_objects (OstreeRepo *self,
|
||||
if (!glnx_renameat (child_dfd_iter.fd, loose_objpath + 3,
|
||||
self->objects_dir_fd, loose_objpath, error))
|
||||
return FALSE;
|
||||
|
||||
renamed_some_object = TRUE;
|
||||
}
|
||||
|
||||
if (renamed_some_object && !self->disable_fsync)
|
||||
{
|
||||
/* Ensure that in the case of a power cut all the directory metadata that
|
||||
we want has reached the disk. In particular, we want this before we
|
||||
update the refs to point to these objects. */
|
||||
glnx_autofd int target_dir_fd = -1;
|
||||
|
||||
loose_objpath[2] = 0;
|
||||
|
||||
if (!glnx_opendirat (self->objects_dir_fd,
|
||||
loose_objpath, FALSE,
|
||||
&target_dir_fd,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
if (fsync (target_dir_fd) == -1)
|
||||
return glnx_throw_errno_prefix (error, "fsync");
|
||||
}
|
||||
}
|
||||
|
||||
/* In case we created any loose object subdirs, make sure they are on disk */
|
||||
if (!self->disable_fsync)
|
||||
{
|
||||
if (fsync (self->objects_dir_fd) == -1)
|
||||
return glnx_throw_errno_prefix (error, "fsync");
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Try to lock a transaction stage directory created by
|
||||
/* Try to lock and delete a transaction stage directory created by
|
||||
* ostree_repo_prepare_transaction().
|
||||
*/
|
||||
static gboolean
|
||||
@@ -1943,6 +1977,9 @@ cleanup_txn_dir (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
const char *errprefix = glnx_strjoina ("Cleaning up txn dir ", path);
|
||||
GLNX_AUTO_PREFIX_ERROR (errprefix, error);
|
||||
|
||||
g_auto(GLnxLockFile) lockfile = { 0, };
|
||||
gboolean did_lock;
|
||||
|
||||
@@ -2008,7 +2045,7 @@ cleanup_tmpdir (OstreeRepo *self,
|
||||
continue;
|
||||
|
||||
/* Handle transaction tmpdirs */
|
||||
if (_ostree_repo_is_locked_tmpdir (dent->d_name))
|
||||
if (_ostree_repo_has_staging_prefix (dent->d_name) && S_ISDIR (stbuf.st_mode))
|
||||
{
|
||||
if (!cleanup_txn_dir (self, dfd_iter.fd, dent->d_name, cancellable, error))
|
||||
return FALSE;
|
||||
@@ -2374,6 +2411,9 @@ ostree_repo_commit_transaction (OstreeRepo *self,
|
||||
if (!rename_pending_loose_objects (self, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!fsync_object_dirs (self, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
g_debug ("txn commit %s", glnx_basename (self->commit_stagedir.path));
|
||||
if (!glnx_tmpdir_delete (&self->commit_stagedir, cancellable, error))
|
||||
return FALSE;
|
||||
@@ -4227,9 +4267,11 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier)
|
||||
if (modifier->xattr_destroy)
|
||||
modifier->xattr_destroy (modifier->xattr_user_data);
|
||||
|
||||
g_clear_object (&modifier->sepolicy);
|
||||
g_clear_pointer (&modifier->devino_cache, (GDestroyNotify)g_hash_table_unref);
|
||||
|
||||
g_clear_object (&modifier->sepolicy);
|
||||
(void) glnx_tmpdir_delete (&modifier->sepolicy_tmpdir, NULL, NULL);
|
||||
|
||||
g_free (modifier);
|
||||
return;
|
||||
}
|
||||
@@ -4279,6 +4321,60 @@ ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier
|
||||
modifier->sepolicy = sepolicy ? g_object_ref (sepolicy) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_repo_commit_modifier_set_sepolicy_from_commit:
|
||||
* @modifier: Commit modifier
|
||||
* @repo: OSTree repo containing @rev
|
||||
* @rev: Find SELinux policy from this base commit
|
||||
* @cancellable:
|
||||
* @error:
|
||||
*
|
||||
* In many cases, one wants to create a "derived" commit from base commit.
|
||||
* SELinux policy labels are part of that base commit. This API allows
|
||||
* one to easily set up SELinux labeling from a base commit.
|
||||
*/
|
||||
gboolean
|
||||
ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier *modifier,
|
||||
OstreeRepo *repo,
|
||||
const char *rev,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("setting sepolicy from commit", error);
|
||||
g_autofree char *commit = NULL;
|
||||
g_autoptr(GFile) root = NULL;
|
||||
if (!ostree_repo_read_commit (repo, rev, &root, &commit, cancellable, error))
|
||||
return FALSE;
|
||||
const char policypath[] = "usr/etc/selinux";
|
||||
g_autoptr(GFile) policyroot = g_file_get_child (root, policypath);
|
||||
if (!g_file_query_exists (policyroot, NULL))
|
||||
return TRUE; /* No policy, nothing to do */
|
||||
|
||||
GLnxTmpDir tmpdir = {0,};
|
||||
if (!glnx_mkdtemp ("ostree-commit-sepolicy-XXXXXX", 0700, &tmpdir, error))
|
||||
return FALSE;
|
||||
if (!glnx_shutil_mkdir_p_at (tmpdir.fd, "usr/etc", 0755, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
OstreeRepoCheckoutAtOptions coopts = {0,};
|
||||
coopts.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
|
||||
coopts.subpath = glnx_strjoina ("/", policypath);
|
||||
|
||||
if (!ostree_repo_checkout_at (repo, &coopts, tmpdir.fd, policypath, commit, cancellable, error))
|
||||
return glnx_prefix_error (error, "policy checkout");
|
||||
|
||||
g_autoptr(OstreeSePolicy) policy = ostree_sepolicy_new_at (tmpdir.fd, cancellable, error);
|
||||
if (!policy)
|
||||
return glnx_prefix_error (error, "reading policy");
|
||||
|
||||
ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
|
||||
/* Transfer ownership */
|
||||
modifier->sepolicy_tmpdir = tmpdir;
|
||||
tmpdir.initialized = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_repo_commit_modifier_set_devino_cache:
|
||||
* @modifier: Modifier
|
||||
|
||||
@@ -24,9 +24,11 @@
|
||||
#include "ostree-core.h"
|
||||
#include "ostree-types.h"
|
||||
|
||||
#ifndef __GI_SCANNER__
|
||||
#ifndef G_GNUC_DEPRECATED_FOR
|
||||
# define G_GNUC_DEPRECATED_FOR(x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
@@ -1358,7 +1358,7 @@ ostree_repo_finder_avahi_init (OstreeRepoFinderAvahi *self)
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_repo-finder_avahi_new:
|
||||
* ostree_repo_finder_avahi_new:
|
||||
* @context: (transfer none) (nullable): a #GMainContext for processing Avahi
|
||||
* events in, or %NULL to use the current thread-default
|
||||
*
|
||||
|
||||
@@ -38,13 +38,17 @@ G_BEGIN_DECLS
|
||||
#define _OSTREE_MAX_OUTSTANDING_FETCHER_REQUESTS 8
|
||||
#define _OSTREE_MAX_OUTSTANDING_DELTAPART_REQUESTS 2
|
||||
|
||||
/* In most cases, writing to disk should be much faster than
|
||||
* fetching from the network, so we shouldn't actually hit
|
||||
* this. But if using pipelining and e.g. pulling over LAN
|
||||
* (or writing to slow media), we can have a runaway
|
||||
* situation towards EMFILE.
|
||||
/* We want some parallelism with disk writes, but we also
|
||||
* want to avoid starting tens or hundreds of threads
|
||||
* (via GTask) all writing to disk. Eventually we may
|
||||
* use io_uring which handles backpressure correctly.
|
||||
* Also, in "immediate fsync" mode, this helps provide
|
||||
* much more backpressure, helping our I/O patterns
|
||||
* be nicer for any concurrent processes, such as etcd
|
||||
* or other databases.
|
||||
* https://github.com/openshift/machine-config-operator/issues/1897
|
||||
* */
|
||||
#define _OSTREE_MAX_OUTSTANDING_WRITE_REQUESTS 16
|
||||
#define _OSTREE_MAX_OUTSTANDING_WRITE_REQUESTS 3
|
||||
|
||||
/* Well-known keys for the additional metadata field in a summary file. */
|
||||
#define OSTREE_SUMMARY_LAST_MODIFIED "ostree.summary.last-modified"
|
||||
@@ -76,6 +80,7 @@ struct OstreeRepoCommitModifier {
|
||||
GDestroyNotify xattr_destroy;
|
||||
gpointer xattr_user_data;
|
||||
|
||||
GLnxTmpDir sepolicy_tmpdir;
|
||||
OstreeSePolicy *sepolicy;
|
||||
GHashTable *devino_cache;
|
||||
};
|
||||
@@ -146,6 +151,7 @@ struct OstreeRepo {
|
||||
GError *writable_error;
|
||||
gboolean in_transaction;
|
||||
gboolean disable_fsync;
|
||||
gboolean per_object_fsync;
|
||||
gboolean disable_xattrs;
|
||||
guint zlib_compression_level;
|
||||
GHashTable *loose_object_devino_hash;
|
||||
@@ -246,7 +252,7 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
_ostree_repo_is_locked_tmpdir (const char *filename);
|
||||
_ostree_repo_has_staging_prefix (const char *filename);
|
||||
|
||||
gboolean
|
||||
_ostree_repo_try_lock_tmpdir (int tmpdir_dfd,
|
||||
@@ -373,9 +379,9 @@ _ostree_repo_verify_commit_internal (OstreeRepo *self,
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
typedef enum {
|
||||
_OSTREE_REPO_IMPORT_FLAGS_NONE,
|
||||
_OSTREE_REPO_IMPORT_FLAGS_TRUSTED,
|
||||
_OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY,
|
||||
_OSTREE_REPO_IMPORT_FLAGS_NONE = 0,
|
||||
_OSTREE_REPO_IMPORT_FLAGS_TRUSTED = (1 << 0),
|
||||
_OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY = (1 << 1),
|
||||
} OstreeRepoImportFlags;
|
||||
|
||||
gboolean
|
||||
@@ -501,4 +507,10 @@ _ostree_tmpf_fsverity (OstreeRepo *self,
|
||||
GLnxTmpfile *tmpf,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
_ostree_repo_verify_bindings (const char *collection_id,
|
||||
const char *ref_name,
|
||||
GVariant *commit,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -21,14 +21,151 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ostree-core.h"
|
||||
#include "ostree-repo-private.h"
|
||||
#include "ostree-fetcher-util.h"
|
||||
#include "ostree-remote-private.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
OSTREE_FETCHER_SECURITY_STATE_CA_PINNED,
|
||||
OSTREE_FETCHER_SECURITY_STATE_TLS,
|
||||
OSTREE_FETCHER_SECURITY_STATE_INSECURE,
|
||||
} OstreeFetcherSecurityState;
|
||||
|
||||
typedef struct {
|
||||
OstreeRepo *repo;
|
||||
int tmpdir_dfd;
|
||||
OstreeRepoPullFlags flags;
|
||||
char *remote_name;
|
||||
char *remote_refspec_name;
|
||||
OstreeRepoMode remote_mode;
|
||||
OstreeFetcher *fetcher;
|
||||
OstreeFetcherSecurityState fetcher_security_state;
|
||||
|
||||
GPtrArray *meta_mirrorlist; /* List of base URIs for fetching metadata */
|
||||
GPtrArray *content_mirrorlist; /* List of base URIs for fetching content */
|
||||
OstreeRepo *remote_repo_local;
|
||||
GPtrArray *localcache_repos; /* Array<OstreeRepo> */
|
||||
|
||||
GMainContext *main_context;
|
||||
GCancellable *cancellable;
|
||||
OstreeAsyncProgress *progress;
|
||||
|
||||
GVariant *extra_headers;
|
||||
char *append_user_agent;
|
||||
|
||||
gboolean dry_run;
|
||||
gboolean dry_run_emitted_progress;
|
||||
gboolean legacy_transaction_resuming;
|
||||
guint n_network_retries;
|
||||
enum {
|
||||
OSTREE_PULL_PHASE_FETCHING_REFS,
|
||||
OSTREE_PULL_PHASE_FETCHING_OBJECTS
|
||||
} phase;
|
||||
gint n_scanned_metadata;
|
||||
|
||||
gboolean gpg_verify;
|
||||
gboolean gpg_verify_summary;
|
||||
gboolean require_static_deltas;
|
||||
gboolean disable_static_deltas;
|
||||
gboolean has_tombstone_commits;
|
||||
|
||||
GBytes *summary_data;
|
||||
GBytes *summary_data_sig;
|
||||
GVariant *summary;
|
||||
GHashTable *summary_deltas_checksums;
|
||||
GHashTable *ref_original_commits; /* Maps checksum to commit, used by timestamp checks */
|
||||
GHashTable *verified_commits; /* Set<checksum> of commits that have been verified */
|
||||
GHashTable *signapi_verified_commits; /* Map<checksum,verification> of commits that have been signapi verified */
|
||||
GHashTable *ref_keyring_map; /* Maps OstreeCollectionRef to keyring remote name */
|
||||
GPtrArray *static_delta_superblocks;
|
||||
GHashTable *expected_commit_sizes; /* Maps commit checksum to known size */
|
||||
GHashTable *commit_to_depth; /* Maps commit checksum maximum depth */
|
||||
GHashTable *scanned_metadata; /* Maps object name to itself */
|
||||
GHashTable *fetched_detached_metadata; /* Map<checksum,GVariant> */
|
||||
GHashTable *requested_metadata; /* Maps object name to itself */
|
||||
GHashTable *requested_content; /* Maps checksum to itself */
|
||||
GHashTable *requested_fallback_content; /* Maps checksum to itself */
|
||||
GHashTable *pending_fetch_metadata; /* Map<ObjectName,FetchObjectData> */
|
||||
GHashTable *pending_fetch_content; /* Map<checksum,FetchObjectData> */
|
||||
GHashTable *pending_fetch_delta_superblocks; /* Set<FetchDeltaSuperData> */
|
||||
GHashTable *pending_fetch_deltaparts; /* Set<FetchStaticDeltaData> */
|
||||
guint n_outstanding_metadata_fetches;
|
||||
guint n_outstanding_metadata_write_requests;
|
||||
guint n_outstanding_content_fetches;
|
||||
guint n_outstanding_content_write_requests;
|
||||
guint n_outstanding_deltapart_fetches;
|
||||
guint n_outstanding_deltapart_write_requests;
|
||||
guint n_total_deltaparts;
|
||||
guint n_total_delta_fallbacks;
|
||||
guint64 fetched_deltapart_size; /* How much of the delta we have now */
|
||||
guint64 total_deltapart_size;
|
||||
guint64 total_deltapart_usize;
|
||||
gint n_requested_metadata;
|
||||
gint n_requested_content;
|
||||
guint n_fetched_deltaparts;
|
||||
guint n_fetched_deltapart_fallbacks;
|
||||
guint n_fetched_metadata;
|
||||
guint n_fetched_content;
|
||||
/* Objects imported via hardlink/reflink/copying or --localcache-repo*/
|
||||
guint n_imported_metadata;
|
||||
guint n_imported_content;
|
||||
|
||||
gboolean timestamp_check; /* Verify commit timestamps */
|
||||
char *timestamp_check_from_rev;
|
||||
int maxdepth;
|
||||
guint64 max_metadata_size;
|
||||
guint64 start_time;
|
||||
|
||||
gboolean is_mirror;
|
||||
gboolean trusted_http_direct;
|
||||
gboolean is_commit_only;
|
||||
OstreeRepoImportFlags importflags;
|
||||
|
||||
GPtrArray *signapi_commit_verifiers;
|
||||
GPtrArray *signapi_summary_verifiers;
|
||||
|
||||
GPtrArray *dirs;
|
||||
|
||||
gboolean have_previous_bytes;
|
||||
guint64 previous_bytes_sec;
|
||||
guint64 previous_total_downloaded;
|
||||
|
||||
GError *cached_async_error;
|
||||
GError **async_error;
|
||||
gboolean caught_error;
|
||||
|
||||
GQueue scan_object_queue;
|
||||
GSource *idle_src;
|
||||
} OtPullData;
|
||||
|
||||
gboolean
|
||||
_ostree_repo_verify_bindings (const char *collection_id,
|
||||
const char *ref_name,
|
||||
GVariant *commit,
|
||||
GError **error);
|
||||
_signapi_init_for_remote (OstreeRepo *repo,
|
||||
const char *remote_name,
|
||||
GPtrArray **out_commit_verifiers,
|
||||
GPtrArray **out_summary_verifiers,
|
||||
GError **error);
|
||||
gboolean
|
||||
_sign_verify_for_remote (GPtrArray *signers,
|
||||
GBytes *signed_data,
|
||||
GVariant *metadata,
|
||||
char **out_success_message,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
_verify_unwritten_commit (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
GVariant *commit,
|
||||
GVariant *detached_metadata,
|
||||
const OstreeCollectionRef *ref,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
_process_gpg_verify_result (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
OstreeGpgVerifyResult *result,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Red Hat, Inc.
|
||||
* Copyright © 2017 Endless Mobile, Inc.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "libglnx.h"
|
||||
#include "ostree.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-repo-pull-private.h"
|
||||
#include "ostree-repo-private.h"
|
||||
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-repo-static-delta-private.h"
|
||||
#include "ostree-metalink.h"
|
||||
#include "ostree-fetcher-util.h"
|
||||
#include "ostree-remote-private.h"
|
||||
#include "ot-fs-utils.h"
|
||||
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <sys/statvfs.h>
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
#include <systemd/sd-journal.h>
|
||||
#endif
|
||||
|
||||
#include "ostree-sign.h"
|
||||
|
||||
static gboolean
|
||||
get_signapi_remote_option (OstreeRepo *repo,
|
||||
OstreeSign *sign,
|
||||
const char *remote_name,
|
||||
const char *keysuffix,
|
||||
char **out_value,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree char *key = g_strdup_printf ("verification-%s-%s", ostree_sign_get_name (sign), keysuffix);
|
||||
return ostree_repo_get_remote_option (repo, remote_name, key, NULL, out_value, error);
|
||||
}
|
||||
|
||||
/* _signapi_load_public_keys:
|
||||
*
|
||||
* Load public keys according remote's configuration:
|
||||
* inlined key passed via config option `verification-<signapi>-key` or
|
||||
* file name with public keys via `verification-<signapi>-file` option.
|
||||
*
|
||||
* If both options are set then load all all public keys
|
||||
* both from file and inlined in config.
|
||||
*
|
||||
* Returns: %FALSE if any source is configured but nothing has been loaded.
|
||||
* Returns: %TRUE if no configuration or any key loaded.
|
||||
* */
|
||||
static gboolean
|
||||
_signapi_load_public_keys (OstreeSign *sign,
|
||||
OstreeRepo *repo,
|
||||
const gchar *remote_name,
|
||||
gboolean required,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree gchar *pk_ascii = NULL;
|
||||
g_autofree gchar *pk_file = NULL;
|
||||
gboolean loaded_from_file = TRUE;
|
||||
gboolean loaded_inlined = TRUE;
|
||||
|
||||
if (!get_signapi_remote_option (repo, sign, remote_name, "file", &pk_file, error))
|
||||
return FALSE;
|
||||
if (!get_signapi_remote_option (repo, sign, remote_name, "key", &pk_ascii, error))
|
||||
return FALSE;
|
||||
|
||||
/* return TRUE if there is no configuration for remote */
|
||||
if ((pk_file == NULL) &&(pk_ascii == NULL))
|
||||
{
|
||||
/* It is expected what remote may have verification file as
|
||||
* a part of configuration. Hence there is not a lot of sense
|
||||
* for automatic resolve of per-remote keystore file as it
|
||||
* used in find_keyring () for GPG.
|
||||
* If it is needed to add the similar mechanism, it is preferable
|
||||
* to pass the path to ostree_sign_load_pk () via GVariant options
|
||||
* and call it here for loading with method and file structure
|
||||
* specific for signature type.
|
||||
*/
|
||||
if (required)
|
||||
return glnx_throw (error, "No keys found for required signapi type %s", ostree_sign_get_name (sign));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (pk_file != NULL)
|
||||
{
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
g_autoptr (GVariantBuilder) builder = NULL;
|
||||
g_autoptr (GVariant) options = NULL;
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
||||
g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (pk_file));
|
||||
options = g_variant_builder_end (builder);
|
||||
|
||||
if (ostree_sign_load_pk (sign, options, &local_error))
|
||||
loaded_from_file = TRUE;
|
||||
else
|
||||
{
|
||||
return glnx_throw (error, "Failed loading '%s' keys from '%s",
|
||||
ostree_sign_get_name (sign), pk_file);
|
||||
}
|
||||
}
|
||||
|
||||
if (pk_ascii != NULL)
|
||||
{
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
g_autoptr (GVariant) pk = g_variant_new_string(pk_ascii);
|
||||
|
||||
/* Add inlined public key */
|
||||
if (loaded_from_file)
|
||||
loaded_inlined = ostree_sign_add_pk (sign, pk, &local_error);
|
||||
else
|
||||
loaded_inlined = ostree_sign_set_pk (sign, pk, &local_error);
|
||||
|
||||
if (!loaded_inlined)
|
||||
{
|
||||
return glnx_throw (error, "Failed loading '%s' keys from inline `verification-key`",
|
||||
ostree_sign_get_name (sign));
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if able to load from any source */
|
||||
if (!(loaded_from_file || loaded_inlined))
|
||||
return glnx_throw (error, "No keys found");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
string_is_gkeyfile_truthy (const char *value,
|
||||
gboolean *out_truth)
|
||||
{
|
||||
/* See https://gitlab.gnome.org/GNOME/glib/-/blob/20fb5bf868added5aec53c013ae85ec78ba2eedc/glib/gkeyfile.c#L4528 */
|
||||
if (g_str_equal (value, "true") || g_str_equal (value, "1"))
|
||||
{
|
||||
*out_truth = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
else if (g_str_equal (value, "false") || g_str_equal (value, "0"))
|
||||
{
|
||||
*out_truth = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
verifiers_from_config (OstreeRepo *repo,
|
||||
const char *remote_name,
|
||||
const char *key,
|
||||
GPtrArray **out_verifiers,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GPtrArray) verifiers = NULL;
|
||||
|
||||
g_autofree char *raw_value = NULL;
|
||||
if (!ostree_repo_get_remote_option (repo, remote_name,
|
||||
key, NULL,
|
||||
&raw_value, error))
|
||||
return FALSE;
|
||||
if (raw_value == NULL || g_str_equal (raw_value, ""))
|
||||
{
|
||||
*out_verifiers = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
gboolean sign_verify_bool = FALSE;
|
||||
/* Is the value "truthy" according to GKeyFile's rules? If so,
|
||||
* then we take this to be "accept signatures from any compiled
|
||||
* type that happens to have keys configured".
|
||||
*/
|
||||
if (string_is_gkeyfile_truthy (raw_value, &sign_verify_bool))
|
||||
{
|
||||
if (sign_verify_bool)
|
||||
{
|
||||
verifiers = ostree_sign_get_all ();
|
||||
for (guint i = 0; i < verifiers->len; i++)
|
||||
{
|
||||
OstreeSign *sign = verifiers->pdata[i];
|
||||
/* Try to load public key(s) according remote's configuration;
|
||||
* this one is optional.
|
||||
*/
|
||||
if (!_signapi_load_public_keys (sign, repo, remote_name, FALSE, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the value isn't "truthy", then it must be an explicit list */
|
||||
g_auto(GStrv) sign_types = NULL;
|
||||
if (!ostree_repo_get_remote_list_option (repo, remote_name,
|
||||
key, &sign_types,
|
||||
error))
|
||||
return FALSE;
|
||||
verifiers = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
||||
for (char **iter = sign_types; iter && *iter; iter++)
|
||||
{
|
||||
const char *sign_type = *iter;
|
||||
OstreeSign *verifier = ostree_sign_get_by_name (sign_type, error);
|
||||
if (!verifier)
|
||||
return FALSE;
|
||||
if (!_signapi_load_public_keys (verifier, repo, remote_name, TRUE, error))
|
||||
return FALSE;
|
||||
g_ptr_array_add (verifiers, verifier);
|
||||
}
|
||||
g_assert_cmpuint (verifiers->len, >=, 1);
|
||||
}
|
||||
|
||||
*out_verifiers = g_steal_pointer (&verifiers);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Create a new array of OstreeSign objects and load the public
|
||||
* keys as described by the remote configuration. If the
|
||||
* remote does not have signing verification enabled, then
|
||||
* the resulting verifier list will be NULL.
|
||||
*/
|
||||
gboolean
|
||||
_signapi_init_for_remote (OstreeRepo *repo,
|
||||
const char *remote_name,
|
||||
GPtrArray **out_commit_verifiers,
|
||||
GPtrArray **out_summary_verifiers,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GPtrArray) commit_verifiers = NULL;
|
||||
g_autoptr(GPtrArray) summary_verifiers = NULL;
|
||||
|
||||
if (!verifiers_from_config (repo, remote_name, "sign-verify", &commit_verifiers, error))
|
||||
return FALSE;
|
||||
if (!verifiers_from_config (repo, remote_name, "sign-verify-summary", &summary_verifiers, error))
|
||||
return FALSE;
|
||||
|
||||
ot_transfer_out_value (out_commit_verifiers, &commit_verifiers);
|
||||
ot_transfer_out_value (out_summary_verifiers, &summary_verifiers);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Iterate over the configured verifiers, and require the commit is signed
|
||||
* by at least one.
|
||||
*/
|
||||
gboolean
|
||||
_sign_verify_for_remote (GPtrArray *verifiers,
|
||||
GBytes *signed_data,
|
||||
GVariant *metadata,
|
||||
char **out_success_message,
|
||||
GError **error)
|
||||
{
|
||||
guint n_invalid_signatures = 0;
|
||||
g_autoptr (GError) last_sig_error = NULL;
|
||||
gboolean found_sig = FALSE;
|
||||
|
||||
g_assert (out_success_message == NULL || *out_success_message == NULL);
|
||||
|
||||
g_assert_cmpuint (verifiers->len, >=, 1);
|
||||
for (guint i = 0; i < verifiers->len; i++)
|
||||
{
|
||||
OstreeSign *sign = verifiers->pdata[i];
|
||||
const gchar *signature_key = ostree_sign_metadata_key (sign);
|
||||
GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format (sign);
|
||||
g_autoptr (GVariant) signatures =
|
||||
g_variant_lookup_value (metadata, signature_key, signature_format);
|
||||
|
||||
/* If not found signatures for requested signature subsystem */
|
||||
if (!signatures)
|
||||
continue;
|
||||
|
||||
found_sig = TRUE;
|
||||
|
||||
g_autofree char *success_message = NULL;
|
||||
/* Return true if any signature fit to pre-loaded public keys.
|
||||
* If no keys configured -- then system configuration will be used */
|
||||
if (!ostree_sign_data_verify (sign,
|
||||
signed_data,
|
||||
signatures,
|
||||
&success_message,
|
||||
last_sig_error ? NULL : &last_sig_error))
|
||||
{
|
||||
n_invalid_signatures++;
|
||||
continue;
|
||||
}
|
||||
/* Accept the first valid signature */
|
||||
if (out_success_message)
|
||||
*out_success_message = g_steal_pointer (&success_message);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!found_sig)
|
||||
return glnx_throw (error, "No signatures found");
|
||||
|
||||
g_assert (last_sig_error);
|
||||
g_propagate_error (error, g_steal_pointer (&last_sig_error));
|
||||
if (n_invalid_signatures > 1)
|
||||
glnx_prefix_error (error, "(%d other invalid signatures)", n_invalid_signatures-1);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
gboolean
|
||||
_process_gpg_verify_result (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
OstreeGpgVerifyResult *result,
|
||||
GError **error)
|
||||
{
|
||||
const char *error_prefix = glnx_strjoina ("Commit ", checksum);
|
||||
GLNX_AUTO_PREFIX_ERROR(error_prefix, error);
|
||||
if (result == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* Allow callers to output the results immediately. */
|
||||
g_signal_emit_by_name (pull_data->repo,
|
||||
"gpg-verify-result",
|
||||
checksum, result);
|
||||
|
||||
if (!ostree_gpg_verify_result_require_valid_signature (result, error))
|
||||
return FALSE;
|
||||
|
||||
|
||||
/* We now check both *before* writing the commit, and after. Because the
|
||||
* behavior used to be only verifiying after writing, we need to handle
|
||||
* the case of "written but not verified". But we also don't want to check
|
||||
* twice, as that'd result in duplicate signals.
|
||||
*/
|
||||
g_hash_table_add (pull_data->verified_commits, g_strdup (checksum));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
gboolean
|
||||
_verify_unwritten_commit (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
GVariant *commit,
|
||||
GVariant *detached_metadata,
|
||||
const OstreeCollectionRef *ref,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* Shouldn't happen, but see comment in process_gpg_verify_result() */
|
||||
if ((!pull_data->gpg_verify || g_hash_table_contains (pull_data->verified_commits, checksum))
|
||||
&& (!pull_data->signapi_commit_verifiers || g_hash_table_contains (pull_data->signapi_verified_commits, checksum)))
|
||||
return TRUE;
|
||||
|
||||
g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit);
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (pull_data->gpg_verify)
|
||||
{
|
||||
const char *keyring_remote = NULL;
|
||||
|
||||
if (ref != NULL)
|
||||
keyring_remote = g_hash_table_lookup (pull_data->ref_keyring_map, ref);
|
||||
if (keyring_remote == NULL)
|
||||
keyring_remote = pull_data->remote_name;
|
||||
|
||||
g_autoptr(OstreeGpgVerifyResult) result =
|
||||
_ostree_repo_gpg_verify_with_metadata (pull_data->repo, signed_data,
|
||||
detached_metadata,
|
||||
keyring_remote,
|
||||
NULL, NULL, cancellable, error);
|
||||
if (!_process_gpg_verify_result (pull_data, checksum, result, error))
|
||||
return FALSE;
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
if (pull_data->signapi_commit_verifiers)
|
||||
{
|
||||
/* Nothing to check if detached metadata is absent */
|
||||
if (detached_metadata == NULL)
|
||||
return glnx_throw (error, "Can't verify commit without detached metadata");
|
||||
|
||||
g_autofree char *success_message = NULL;
|
||||
if (!_sign_verify_for_remote (pull_data->signapi_commit_verifiers, signed_data, detached_metadata, &success_message, error))
|
||||
return glnx_prefix_error (error, "Can't verify commit");
|
||||
|
||||
/* Mark the commit as verified to avoid double verification
|
||||
* see process_verify_result () for rationale */
|
||||
g_hash_table_insert (pull_data->signapi_verified_commits, g_strdup (checksum), g_steal_pointer (&success_message));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
+262
-342
@@ -30,16 +30,12 @@
|
||||
#include "ostree.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-repo-pull-private.h"
|
||||
#include "ostree-repo-private.h"
|
||||
|
||||
#ifdef HAVE_LIBCURL_OR_LIBSOUP
|
||||
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-repo-static-delta-private.h"
|
||||
#include "ostree-metalink.h"
|
||||
#include "ostree-fetcher-util.h"
|
||||
#include "ostree-remote-private.h"
|
||||
#include "ot-fs-utils.h"
|
||||
|
||||
#include "ostree-repo-finder.h"
|
||||
#include "ostree-repo-finder-config.h"
|
||||
@@ -65,114 +61,6 @@
|
||||
* `n-network-retries` pull option. */
|
||||
#define DEFAULT_N_NETWORK_RETRIES 5
|
||||
|
||||
typedef enum {
|
||||
OSTREE_FETCHER_SECURITY_STATE_CA_PINNED,
|
||||
OSTREE_FETCHER_SECURITY_STATE_TLS,
|
||||
OSTREE_FETCHER_SECURITY_STATE_INSECURE,
|
||||
} OstreeFetcherSecurityState;
|
||||
|
||||
typedef struct {
|
||||
OstreeRepo *repo;
|
||||
int tmpdir_dfd;
|
||||
OstreeRepoPullFlags flags;
|
||||
char *remote_name;
|
||||
char *remote_refspec_name;
|
||||
OstreeRepoMode remote_mode;
|
||||
OstreeFetcher *fetcher;
|
||||
OstreeFetcherSecurityState fetcher_security_state;
|
||||
|
||||
GPtrArray *meta_mirrorlist; /* List of base URIs for fetching metadata */
|
||||
GPtrArray *content_mirrorlist; /* List of base URIs for fetching content */
|
||||
OstreeRepo *remote_repo_local;
|
||||
GPtrArray *localcache_repos; /* Array<OstreeRepo> */
|
||||
|
||||
GMainContext *main_context;
|
||||
GCancellable *cancellable;
|
||||
OstreeAsyncProgress *progress;
|
||||
|
||||
GVariant *extra_headers;
|
||||
char *append_user_agent;
|
||||
|
||||
gboolean dry_run;
|
||||
gboolean dry_run_emitted_progress;
|
||||
gboolean legacy_transaction_resuming;
|
||||
guint n_network_retries;
|
||||
enum {
|
||||
OSTREE_PULL_PHASE_FETCHING_REFS,
|
||||
OSTREE_PULL_PHASE_FETCHING_OBJECTS
|
||||
} phase;
|
||||
gint n_scanned_metadata;
|
||||
|
||||
gboolean gpg_verify;
|
||||
gboolean gpg_verify_summary;
|
||||
gboolean require_static_deltas;
|
||||
gboolean disable_static_deltas;
|
||||
gboolean has_tombstone_commits;
|
||||
|
||||
GBytes *summary_data;
|
||||
GBytes *summary_data_sig;
|
||||
GVariant *summary;
|
||||
GHashTable *summary_deltas_checksums;
|
||||
GHashTable *ref_original_commits; /* Maps checksum to commit, used by timestamp checks */
|
||||
GHashTable *verified_commits; /* Set<checksum> of commits that have been verified */
|
||||
GHashTable *ref_keyring_map; /* Maps OstreeCollectionRef to keyring remote name */
|
||||
GPtrArray *static_delta_superblocks;
|
||||
GHashTable *expected_commit_sizes; /* Maps commit checksum to known size */
|
||||
GHashTable *commit_to_depth; /* Maps commit checksum maximum depth */
|
||||
GHashTable *scanned_metadata; /* Maps object name to itself */
|
||||
GHashTable *fetched_detached_metadata; /* Map<checksum,GVariant> */
|
||||
GHashTable *requested_metadata; /* Maps object name to itself */
|
||||
GHashTable *requested_content; /* Maps checksum to itself */
|
||||
GHashTable *requested_fallback_content; /* Maps checksum to itself */
|
||||
GHashTable *pending_fetch_metadata; /* Map<ObjectName,FetchObjectData> */
|
||||
GHashTable *pending_fetch_content; /* Map<checksum,FetchObjectData> */
|
||||
GHashTable *pending_fetch_delta_superblocks; /* Set<FetchDeltaSuperData> */
|
||||
GHashTable *pending_fetch_deltaparts; /* Set<FetchStaticDeltaData> */
|
||||
guint n_outstanding_metadata_fetches;
|
||||
guint n_outstanding_metadata_write_requests;
|
||||
guint n_outstanding_content_fetches;
|
||||
guint n_outstanding_content_write_requests;
|
||||
guint n_outstanding_deltapart_fetches;
|
||||
guint n_outstanding_deltapart_write_requests;
|
||||
guint n_total_deltaparts;
|
||||
guint n_total_delta_fallbacks;
|
||||
guint64 fetched_deltapart_size; /* How much of the delta we have now */
|
||||
guint64 total_deltapart_size;
|
||||
guint64 total_deltapart_usize;
|
||||
gint n_requested_metadata;
|
||||
gint n_requested_content;
|
||||
guint n_fetched_deltaparts;
|
||||
guint n_fetched_deltapart_fallbacks;
|
||||
guint n_fetched_metadata;
|
||||
guint n_fetched_content;
|
||||
/* Objects imported via hardlink/reflink/copying or --localcache-repo*/
|
||||
guint n_imported_metadata;
|
||||
guint n_imported_content;
|
||||
|
||||
gboolean timestamp_check; /* Verify commit timestamps */
|
||||
int maxdepth;
|
||||
guint64 max_metadata_size;
|
||||
guint64 start_time;
|
||||
|
||||
gboolean is_mirror;
|
||||
gboolean trusted_http_direct;
|
||||
gboolean is_commit_only;
|
||||
OstreeRepoImportFlags importflags;
|
||||
|
||||
GPtrArray *dirs;
|
||||
|
||||
gboolean have_previous_bytes;
|
||||
guint64 previous_bytes_sec;
|
||||
guint64 previous_total_downloaded;
|
||||
|
||||
GError *cached_async_error;
|
||||
GError **async_error;
|
||||
gboolean caught_error;
|
||||
|
||||
GQueue scan_object_queue;
|
||||
GSource *idle_src;
|
||||
} OtPullData;
|
||||
|
||||
typedef struct {
|
||||
OtPullData *pull_data;
|
||||
GVariant *object;
|
||||
@@ -260,14 +148,6 @@ static gboolean scan_one_metadata_object (OtPullData *pull_data,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
static void scan_object_queue_data_free (ScanObjectQueueData *scan_data);
|
||||
static gboolean
|
||||
ostree_verify_unwritten_commit (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
GVariant *commit,
|
||||
GVariant *detached_metadata,
|
||||
const OstreeCollectionRef *ref,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
static gboolean
|
||||
update_progress (gpointer user_data)
|
||||
@@ -1119,7 +999,10 @@ content_fetch_on_complete (GObject *object,
|
||||
if (!ostree_content_stream_parse (TRUE, tmpf_input, stbuf.st_size, FALSE,
|
||||
&file_in, &file_info, &xattrs,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
{
|
||||
g_prefix_error (error, "Parsing %s: ", checksum_obj);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (verifying_bareuseronly)
|
||||
{
|
||||
@@ -1309,8 +1192,8 @@ meta_fetch_on_complete (GObject *object,
|
||||
* metadata into this hash.
|
||||
*/
|
||||
GVariant *detached_data = g_hash_table_lookup (pull_data->fetched_detached_metadata, checksum);
|
||||
if (!ostree_verify_unwritten_commit (pull_data, checksum, metadata, detached_data,
|
||||
fetch_data->requested_ref, pull_data->cancellable, error))
|
||||
if (!_verify_unwritten_commit (pull_data, checksum, metadata, detached_data,
|
||||
fetch_data->requested_ref, pull_data->cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!ostree_repo_mark_commit_partial (pull_data->repo, checksum, TRUE, error))
|
||||
@@ -1434,75 +1317,6 @@ static_deltapart_fetch_on_complete (GObject *object,
|
||||
g_clear_pointer (&fetch_data, fetch_static_delta_data_free);
|
||||
}
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
static gboolean
|
||||
process_verify_result (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
OstreeGpgVerifyResult *result,
|
||||
GError **error)
|
||||
{
|
||||
const char *error_prefix = glnx_strjoina ("Commit ", checksum);
|
||||
GLNX_AUTO_PREFIX_ERROR(error_prefix, error);
|
||||
if (result == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* Allow callers to output the results immediately. */
|
||||
g_signal_emit_by_name (pull_data->repo,
|
||||
"gpg-verify-result",
|
||||
checksum, result);
|
||||
|
||||
if (!ostree_gpg_verify_result_require_valid_signature (result, error))
|
||||
return FALSE;
|
||||
|
||||
|
||||
/* We now check both *before* writing the commit, and after. Because the
|
||||
* behavior used to be only verifiying after writing, we need to handle
|
||||
* the case of "written but not verified". But we also don't want to check
|
||||
* twice, as that'd result in duplicate signals.
|
||||
*/
|
||||
g_hash_table_add (pull_data->verified_commits, g_strdup (checksum));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
static gboolean
|
||||
ostree_verify_unwritten_commit (OtPullData *pull_data,
|
||||
const char *checksum,
|
||||
GVariant *commit,
|
||||
GVariant *detached_metadata,
|
||||
const OstreeCollectionRef *ref,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (pull_data->gpg_verify)
|
||||
{
|
||||
const char *keyring_remote = NULL;
|
||||
|
||||
/* Shouldn't happen, but see comment in process_verify_result() */
|
||||
if (g_hash_table_contains (pull_data->verified_commits, checksum))
|
||||
return TRUE;
|
||||
|
||||
if (ref != NULL)
|
||||
keyring_remote = g_hash_table_lookup (pull_data->ref_keyring_map, ref);
|
||||
if (keyring_remote == NULL)
|
||||
keyring_remote = pull_data->remote_name;
|
||||
|
||||
g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit);
|
||||
g_autoptr(OstreeGpgVerifyResult) result =
|
||||
_ostree_repo_gpg_verify_with_metadata (pull_data->repo, signed_data,
|
||||
detached_metadata,
|
||||
keyring_remote,
|
||||
NULL, NULL, cancellable, error);
|
||||
if (!process_verify_result (pull_data, checksum, result, error))
|
||||
return FALSE;
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
commitstate_is_partial (OtPullData *pull_data,
|
||||
OstreeRepoCommitState commitstate)
|
||||
@@ -1513,109 +1327,6 @@ commitstate_is_partial (OtPullData *pull_data,
|
||||
|
||||
#endif /* HAVE_LIBCURL_OR_LIBSOUP */
|
||||
|
||||
/**
|
||||
* _ostree_repo_verify_bindings:
|
||||
* @collection_id: (nullable): Locally specified collection ID for the remote
|
||||
* the @commit was retrieved from, or %NULL if none is configured
|
||||
* @ref_name: (nullable): Ref name the commit was retrieved using, or %NULL if
|
||||
* the commit was retrieved by checksum
|
||||
* @commit: Commit data to check
|
||||
* @error: Return location for a #GError, or %NULL
|
||||
*
|
||||
* Verify the ref and collection bindings.
|
||||
*
|
||||
* The ref binding is verified only if it exists. But if we have the
|
||||
* collection ID specified in the remote configuration (@collection_id is
|
||||
* non-%NULL) then the ref binding must exist, otherwise the verification will
|
||||
* fail. Parts of the verification can be skipped by passing %NULL to the
|
||||
* @ref_name parameter (in case we requested a checksum directly, without
|
||||
* looking it up from a ref).
|
||||
*
|
||||
* The collection binding is verified only when we have collection ID
|
||||
* specified in the remote configuration. If it is specified, then the
|
||||
* binding must exist and must be equal to the remote repository
|
||||
* collection ID.
|
||||
*
|
||||
* Returns: %TRUE if bindings are correct, %FALSE otherwise
|
||||
* Since: 2017.14
|
||||
*/
|
||||
gboolean
|
||||
_ostree_repo_verify_bindings (const char *collection_id,
|
||||
const char *ref_name,
|
||||
GVariant *commit,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GVariant) metadata = g_variant_get_child_value (commit, 0);
|
||||
g_autofree const char **refs = NULL;
|
||||
if (!g_variant_lookup (metadata,
|
||||
OSTREE_COMMIT_META_KEY_REF_BINDING,
|
||||
"^a&s",
|
||||
&refs))
|
||||
{
|
||||
/* Early return here - if the remote collection ID is NULL, then
|
||||
* we certainly will not verify the collection binding in the
|
||||
* commit.
|
||||
*/
|
||||
if (collection_id == NULL)
|
||||
return TRUE;
|
||||
|
||||
return glnx_throw (error,
|
||||
"Expected commit metadata to have ref "
|
||||
"binding information, found none");
|
||||
}
|
||||
|
||||
if (ref_name != NULL)
|
||||
{
|
||||
if (!g_strv_contains ((const char *const *) refs, ref_name))
|
||||
{
|
||||
g_autoptr(GString) refs_dump = g_string_new (NULL);
|
||||
const char *refs_str;
|
||||
|
||||
if (refs != NULL && (*refs) != NULL)
|
||||
{
|
||||
for (const char **iter = refs; *iter != NULL; ++iter)
|
||||
{
|
||||
const char *ref = *iter;
|
||||
|
||||
if (refs_dump->len > 0)
|
||||
g_string_append (refs_dump, ", ");
|
||||
g_string_append_printf (refs_dump, "‘%s’", ref);
|
||||
}
|
||||
|
||||
refs_str = refs_dump->str;
|
||||
}
|
||||
else
|
||||
{
|
||||
refs_str = "no refs";
|
||||
}
|
||||
|
||||
return glnx_throw (error, "Commit has no requested ref ‘%s’ "
|
||||
"in ref binding metadata (%s)",
|
||||
ref_name, refs_str);
|
||||
}
|
||||
}
|
||||
|
||||
if (collection_id != NULL)
|
||||
{
|
||||
const char *collection_id_binding;
|
||||
if (!g_variant_lookup (metadata,
|
||||
OSTREE_COMMIT_META_KEY_COLLECTION_BINDING,
|
||||
"&s",
|
||||
&collection_id_binding))
|
||||
return glnx_throw (error,
|
||||
"Expected commit metadata to have collection ID "
|
||||
"binding information, found none");
|
||||
if (!g_str_equal (collection_id_binding, collection_id))
|
||||
return glnx_throw (error,
|
||||
"Commit has collection ID ‘%s’ in collection binding "
|
||||
"metadata, while the remote it came from has "
|
||||
"collection ID ‘%s’",
|
||||
collection_id_binding, collection_id);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Reads the collection-id of a given remote from the repo
|
||||
* configuration.
|
||||
*/
|
||||
@@ -1824,11 +1535,51 @@ scan_commit_object (OtPullData *pull_data,
|
||||
keyring_remote,
|
||||
cancellable,
|
||||
error);
|
||||
if (!process_verify_result (pull_data, checksum, result, error))
|
||||
if (!_process_gpg_verify_result (pull_data, checksum, result, error))
|
||||
return FALSE;
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
if (pull_data->signapi_commit_verifiers &&
|
||||
!g_hash_table_contains (pull_data->signapi_verified_commits, checksum))
|
||||
{
|
||||
g_autoptr(GError) last_verification_error = NULL;
|
||||
gboolean found_any_signature = FALSE;
|
||||
gboolean found_valid_signature = FALSE;
|
||||
g_autofree char *success_message = NULL;
|
||||
|
||||
for (guint i = 0; i < pull_data->signapi_commit_verifiers->len; i++)
|
||||
{
|
||||
OstreeSign *sign = pull_data->signapi_commit_verifiers->pdata[i];
|
||||
|
||||
found_any_signature = TRUE;
|
||||
|
||||
/* Set return to true if any sign fit */
|
||||
if (ostree_sign_commit_verify (sign,
|
||||
pull_data->repo,
|
||||
checksum,
|
||||
&success_message,
|
||||
cancellable,
|
||||
last_verification_error ? NULL : &last_verification_error))
|
||||
{
|
||||
found_valid_signature = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_any_signature)
|
||||
return glnx_throw (error, "No signatures found for commit %s", checksum);
|
||||
|
||||
if (!found_valid_signature)
|
||||
{
|
||||
g_assert (last_verification_error);
|
||||
g_propagate_error (error, g_steal_pointer (&last_verification_error));
|
||||
return glnx_prefix_error (error, "Can't verify commit %s", checksum);
|
||||
}
|
||||
g_assert (success_message);
|
||||
g_hash_table_insert (pull_data->signapi_verified_commits, g_strdup (checksum), g_steal_pointer (&success_message));
|
||||
}
|
||||
|
||||
/* If we found a legacy transaction flag, assume we have to scan.
|
||||
* We always do a scan of dirtree objects; see
|
||||
* https://github.com/ostreedev/ostree/issues/543
|
||||
@@ -1848,6 +1599,7 @@ scan_commit_object (OtPullData *pull_data,
|
||||
commit, error))
|
||||
return glnx_prefix_error (error, "Commit %s", checksum);
|
||||
|
||||
guint64 new_ts = ostree_commit_get_timestamp (commit);
|
||||
if (pull_data->timestamp_check)
|
||||
{
|
||||
/* We don't support timestamp checking while recursing right now */
|
||||
@@ -1866,11 +1618,22 @@ scan_commit_object (OtPullData *pull_data,
|
||||
return glnx_prefix_error (error, "Reading %s for timestamp-check", ref->ref_name);
|
||||
|
||||
guint64 orig_ts = ostree_commit_get_timestamp (orig_commit);
|
||||
guint64 new_ts = ostree_commit_get_timestamp (commit);
|
||||
if (!_ostree_compare_timestamps (orig_rev, orig_ts, checksum, new_ts, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if (pull_data->timestamp_check_from_rev)
|
||||
{
|
||||
g_autoptr(GVariant) commit = NULL;
|
||||
if (!ostree_repo_load_commit (pull_data->repo, pull_data->timestamp_check_from_rev,
|
||||
&commit, NULL, error))
|
||||
return glnx_prefix_error (error, "Reading %s for timestamp-check-from-rev",
|
||||
pull_data->timestamp_check_from_rev);
|
||||
|
||||
guint64 ts = ostree_commit_get_timestamp (commit);
|
||||
if (!_ostree_compare_timestamps (pull_data->timestamp_check_from_rev, ts, checksum, new_ts, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If we found a legacy transaction flag, assume all commits are partial */
|
||||
gboolean is_partial = commitstate_is_partial (pull_data, commitstate);
|
||||
@@ -2413,8 +2176,8 @@ process_one_static_delta (OtPullData *pull_data,
|
||||
g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_revision, to_revision, "commitmeta");
|
||||
g_autoptr(GVariant) detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}"));
|
||||
|
||||
if (!ostree_verify_unwritten_commit (pull_data, to_revision, to_commit, detached_data,
|
||||
ref, cancellable, error))
|
||||
if (!_verify_unwritten_commit (pull_data, to_revision, to_commit, detached_data,
|
||||
ref, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (detached_data && !ostree_repo_write_commit_detached_metadata (pull_data->repo,
|
||||
@@ -3520,11 +3283,15 @@ initiate_request (OtPullData *pull_data,
|
||||
* * override-remote-name (s): If local, add this remote to refspec
|
||||
* * gpg-verify (b): GPG verify commits
|
||||
* * gpg-verify-summary (b): GPG verify summary
|
||||
* * disable-sign-verify (b): Disable signapi verification of commits
|
||||
* * disable-sign-verify-summary (b): Disable signapi verification of the summary
|
||||
* * depth (i): How far in the history to traverse; default is 0, -1 means infinite
|
||||
* * per-object-fsync (b): Perform disk writes more slowly, avoiding a single large I/O sync
|
||||
* * disable-static-deltas (b): Do not use static deltas
|
||||
* * require-static-deltas (b): Require static deltas
|
||||
* * override-commit-ids (as): Array of specific commit IDs to fetch for refs
|
||||
* * timestamp-check (b): Verify commit timestamps are newer than current (when pulling via ref); Since: 2017.11
|
||||
* * timestamp-check-from-rev (s): Verify that all fetched commit timestamps are newer than timestamp of given rev; Since: 2020.4
|
||||
* * metadata-size-restriction (t): Restrict metadata objects to a maximum number of bytes; 0 to disable. Since: 2018.9
|
||||
* * dry-run (b): Only print information on what will be downloaded (requires static deltas)
|
||||
* * override-url (s): Fetch objects from this URL if remote specifies no metalink in options
|
||||
@@ -3574,11 +3341,14 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_autoptr(GVariantIter) collection_refs_iter = NULL;
|
||||
g_autofree char **override_commit_ids = NULL;
|
||||
g_autoptr(GSource) update_timeout = NULL;
|
||||
gboolean opt_per_object_fsync = FALSE;
|
||||
gboolean opt_gpg_verify_set = FALSE;
|
||||
gboolean opt_gpg_verify_summary_set = FALSE;
|
||||
gboolean opt_collection_refs_set = FALSE;
|
||||
gboolean opt_n_network_retries_set = FALSE;
|
||||
gboolean opt_ref_keyring_map_set = FALSE;
|
||||
gboolean disable_sign_verify = FALSE;
|
||||
gboolean disable_sign_verify_summary = FALSE;
|
||||
const char *main_collection_id = NULL;
|
||||
const char *url_override = NULL;
|
||||
gboolean inherit_transaction = FALSE;
|
||||
@@ -3590,6 +3360,7 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
* value, otherwise NULL. Used for logging.
|
||||
*/
|
||||
const char *the_ref_to_fetch = NULL;
|
||||
OstreeRepoTransactionStats tstats = { 0, };
|
||||
|
||||
/* Default */
|
||||
pull_data->max_metadata_size = OSTREE_MAX_METADATA_SIZE;
|
||||
@@ -3610,17 +3381,21 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_variant_lookup (options, "gpg-verify", "b", &pull_data->gpg_verify);
|
||||
opt_gpg_verify_summary_set =
|
||||
g_variant_lookup (options, "gpg-verify-summary", "b", &pull_data->gpg_verify_summary);
|
||||
g_variant_lookup (options, "disable-sign-verify", "b", &disable_sign_verify);
|
||||
g_variant_lookup (options, "disable-sign-verify-summary", "b", &disable_sign_verify_summary);
|
||||
(void) g_variant_lookup (options, "depth", "i", &pull_data->maxdepth);
|
||||
(void) g_variant_lookup (options, "disable-static-deltas", "b", &pull_data->disable_static_deltas);
|
||||
(void) g_variant_lookup (options, "require-static-deltas", "b", &pull_data->require_static_deltas);
|
||||
(void) g_variant_lookup (options, "override-commit-ids", "^a&s", &override_commit_ids);
|
||||
(void) g_variant_lookup (options, "dry-run", "b", &pull_data->dry_run);
|
||||
(void) g_variant_lookup (options, "per-object-fsync", "b", &opt_per_object_fsync);
|
||||
(void) g_variant_lookup (options, "override-url", "&s", &url_override);
|
||||
(void) g_variant_lookup (options, "inherit-transaction", "b", &inherit_transaction);
|
||||
(void) g_variant_lookup (options, "http-headers", "@a(ss)", &pull_data->extra_headers);
|
||||
(void) g_variant_lookup (options, "update-frequency", "u", &update_frequency);
|
||||
(void) g_variant_lookup (options, "localcache-repos", "^a&s", &opt_localcache_repos);
|
||||
(void) g_variant_lookup (options, "timestamp-check", "b", &pull_data->timestamp_check);
|
||||
(void) g_variant_lookup (options, "timestamp-check-from-rev", "s", &pull_data->timestamp_check_from_rev);
|
||||
(void) g_variant_lookup (options, "max-metadata-size", "t", &pull_data->max_metadata_size);
|
||||
(void) g_variant_lookup (options, "append-user-agent", "s", &pull_data->append_user_agent);
|
||||
opt_n_network_retries_set =
|
||||
@@ -3634,10 +3409,10 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
|
||||
#ifdef OSTREE_DISABLE_GPGME
|
||||
/* Explicitly fail here if gpg verification is requested and we have no GPG support */
|
||||
if (opt_gpg_verify_set || opt_gpg_verify_summary_set)
|
||||
if (pull_data->gpg_verify || pull_data->gpg_verify_summary)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
"'%s': GPG feature is disabled at build time",
|
||||
__FUNCTION__);
|
||||
goto out;
|
||||
}
|
||||
@@ -3682,6 +3457,10 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
pull_data->main_context = g_main_context_ref_thread_default ();
|
||||
pull_data->flags = flags;
|
||||
|
||||
/* TODO: Avoid mutating the repo object */
|
||||
if (opt_per_object_fsync)
|
||||
self->per_object_fsync = TRUE;
|
||||
|
||||
if (!opt_n_network_retries_set)
|
||||
pull_data->n_network_retries = DEFAULT_N_NETWORK_RETRIES;
|
||||
|
||||
@@ -3702,6 +3481,8 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
(GDestroyNotify)g_free);
|
||||
pull_data->verified_commits = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, NULL);
|
||||
pull_data->signapi_verified_commits = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
(GDestroyNotify)g_free, NULL);
|
||||
pull_data->ref_keyring_map = g_hash_table_new_full (ostree_collection_ref_hash, ostree_collection_ref_equal,
|
||||
(GDestroyNotify)ostree_collection_ref_free, (GDestroyNotify)g_free);
|
||||
pull_data->scanned_metadata = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal,
|
||||
@@ -3759,7 +3540,9 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
/* For compatibility with pull-local, don't gpg verify local
|
||||
* pulls by default.
|
||||
*/
|
||||
if ((pull_data->gpg_verify || pull_data->gpg_verify_summary) &&
|
||||
if ((pull_data->gpg_verify ||
|
||||
pull_data->gpg_verify_summary
|
||||
) &&
|
||||
pull_data->remote_name == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
@@ -3774,7 +3557,6 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_free (pull_data->remote_name);
|
||||
pull_data->remote_name = g_strdup (remote_name_or_baseurl);
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
/* Fetch GPG verification settings from remote if it wasn't already
|
||||
* explicitly set in the options. */
|
||||
if (!opt_gpg_verify_set)
|
||||
@@ -3786,7 +3568,6 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
if (!ostree_repo_remote_get_gpg_verify_summary (self, pull_data->remote_name,
|
||||
&pull_data->gpg_verify_summary, error))
|
||||
goto out;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
/* NOTE: If changing this, see the matching implementation in
|
||||
* ostree-sysroot-upgrader.c
|
||||
@@ -3805,6 +3586,15 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
if (pull_data->remote_name && !(disable_sign_verify && disable_sign_verify_summary))
|
||||
{
|
||||
if (!_signapi_init_for_remote (pull_data->repo, pull_data->remote_name,
|
||||
&pull_data->signapi_commit_verifiers,
|
||||
&pull_data->signapi_summary_verifiers,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pull_data->phase = OSTREE_PULL_PHASE_FETCHING_REFS;
|
||||
|
||||
if (!reinitialize_fetcher (pull_data, remote_name_or_baseurl, error))
|
||||
@@ -4168,6 +3958,65 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
}
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
if (pull_data->signapi_summary_verifiers)
|
||||
{
|
||||
if (!bytes_sig && pull_data->signapi_summary_verifiers)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Signatures verification enabled, but no summary.sig found (use sign-verify-summary=false in remote config to disable)");
|
||||
goto out;
|
||||
}
|
||||
if (bytes_summary && bytes_sig)
|
||||
{
|
||||
g_autoptr(GVariant) signatures = NULL;
|
||||
g_autoptr(GError) temp_error = NULL;
|
||||
|
||||
signatures = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT,
|
||||
bytes_sig, FALSE);
|
||||
|
||||
|
||||
g_assert (pull_data->signapi_summary_verifiers);
|
||||
if (!_sign_verify_for_remote (pull_data->signapi_summary_verifiers, bytes_summary, signatures, NULL, &temp_error))
|
||||
{
|
||||
if (summary_from_cache)
|
||||
{
|
||||
/* The cached summary doesn't match, fetch a new one and verify again */
|
||||
if ((self->test_error_flags & OSTREE_REPO_TEST_ERROR_INVALID_CACHE) > 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Remote %s cached summary invalid and "
|
||||
"OSTREE_REPO_TEST_ERROR_INVALID_CACHE specified",
|
||||
pull_data->remote_name);
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
g_debug ("Remote %s cached summary invalid, pulling new version",
|
||||
pull_data->remote_name);
|
||||
|
||||
summary_from_cache = FALSE;
|
||||
g_clear_pointer (&bytes_summary, (GDestroyNotify)g_bytes_unref);
|
||||
if (!_ostree_fetcher_mirrored_request_to_membuf (pull_data->fetcher,
|
||||
pull_data->meta_mirrorlist,
|
||||
"summary",
|
||||
OSTREE_FETCHER_REQUEST_OPTIONAL_CONTENT,
|
||||
pull_data->n_network_retries,
|
||||
&bytes_summary,
|
||||
OSTREE_MAX_METADATA_SIZE,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!_sign_verify_for_remote (pull_data->signapi_summary_verifiers, bytes_summary, signatures, NULL, error))
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_propagate_error (error, g_steal_pointer (&temp_error));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_summary)
|
||||
{
|
||||
pull_data->summary_data = g_bytes_ref (bytes_summary);
|
||||
@@ -4427,6 +4276,18 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_steal_pointer (&checksum));
|
||||
}
|
||||
|
||||
/* Resolve refs to a checksum if necessary */
|
||||
if (pull_data->timestamp_check_from_rev &&
|
||||
!ostree_validate_checksum_string (pull_data->timestamp_check_from_rev, NULL))
|
||||
{
|
||||
g_autofree char *from_rev = NULL;
|
||||
if (!ostree_repo_resolve_rev (pull_data->repo, pull_data->timestamp_check_from_rev, FALSE,
|
||||
&from_rev, error))
|
||||
goto out;
|
||||
g_free (pull_data->timestamp_check_from_rev);
|
||||
pull_data->timestamp_check_from_rev = g_steal_pointer (&from_rev);
|
||||
}
|
||||
|
||||
g_hash_table_unref (requested_refs_to_fetch);
|
||||
requested_refs_to_fetch = g_steal_pointer (&updated_requested_refs_to_fetch);
|
||||
if (g_hash_table_size (requested_refs_to_fetch) == 1)
|
||||
@@ -4567,7 +4428,8 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
if (pull_data->is_mirror && pull_data->summary_data && !refs_to_fetch && !configured_branches)
|
||||
if (pull_data->is_mirror && pull_data->summary_data &&
|
||||
!refs_to_fetch && !opt_collection_refs_set && !configured_branches)
|
||||
{
|
||||
GLnxFileReplaceFlags replaceflag =
|
||||
pull_data->repo->disable_fsync ? GLNX_FILE_REPLACE_NODATASYNC : 0;
|
||||
@@ -4590,7 +4452,7 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
}
|
||||
|
||||
if (!inherit_transaction &&
|
||||
!ostree_repo_commit_transaction (pull_data->repo, NULL, cancellable, error))
|
||||
!ostree_repo_commit_transaction (pull_data->repo, &tstats, cancellable, error))
|
||||
goto out;
|
||||
|
||||
end_time = g_get_monotonic_time ();
|
||||
@@ -4632,6 +4494,11 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
shift == 1 ? "B" : "KiB",
|
||||
(guint) ((end_time - pull_data->start_time) / G_USEC_PER_SEC));
|
||||
}
|
||||
if (!inherit_transaction)
|
||||
{
|
||||
g_autofree char *bytes_written = g_format_size (tstats.content_bytes_written);
|
||||
g_string_append_printf (buf, "; %s content written", bytes_written);
|
||||
}
|
||||
|
||||
ostree_async_progress_set_status (pull_data->progress, buf->str);
|
||||
}
|
||||
@@ -4647,22 +4514,26 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_string_append_printf (msg, "libostree pull from '%s' for %u refs complete",
|
||||
pull_data->remote_name, g_hash_table_size (requested_refs_to_fetch));
|
||||
|
||||
const char *verify_state;
|
||||
const char *gpg_verify_state;
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (pull_data->gpg_verify_summary)
|
||||
{
|
||||
if (pull_data->gpg_verify)
|
||||
verify_state = "summary+commit";
|
||||
gpg_verify_state = "summary+commit";
|
||||
else
|
||||
verify_state = "summary-only";
|
||||
gpg_verify_state = "summary-only";
|
||||
}
|
||||
else
|
||||
verify_state = (pull_data->gpg_verify ? "commit" : "disabled");
|
||||
g_string_append_printf (msg, "\nsecurity: GPG: %s ", verify_state);
|
||||
gpg_verify_state = (pull_data->gpg_verify ? "commit" : "disabled");
|
||||
|
||||
#else
|
||||
verify_state = "disabled";
|
||||
g_string_append_printf (msg, "\nsecurity: %s ", verify_state);
|
||||
gpg_verify_state = "disabled";
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
g_string_append_printf (msg, "\nsecurity: GPG: %s ", gpg_verify_state);
|
||||
|
||||
const char *sign_verify_state;
|
||||
sign_verify_state = (pull_data->signapi_commit_verifiers ? "commit" : "disabled");
|
||||
g_string_append_printf (msg, "\nsecurity: SIGN: %s ", sign_verify_state);
|
||||
|
||||
OstreeFetcherURI *first_uri = pull_data->meta_mirrorlist->pdata[0];
|
||||
g_autofree char *first_scheme = _ostree_fetcher_uri_get_scheme (first_uri);
|
||||
@@ -4694,11 +4565,16 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
const guint n_seconds = (guint) ((end_time - pull_data->start_time) / G_USEC_PER_SEC);
|
||||
g_autofree char *formatted_xferred = g_format_size (bytes_transferred);
|
||||
g_string_append_printf (msg, "\ntransfer: secs: %u size: %s", n_seconds, formatted_xferred);
|
||||
if (pull_data->signapi_commit_verifiers)
|
||||
{
|
||||
g_assert_cmpuint (g_hash_table_size (pull_data->signapi_verified_commits), >, 0);
|
||||
}
|
||||
|
||||
ot_journal_send ("MESSAGE=%s", msg->str,
|
||||
"MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(OSTREE_MESSAGE_FETCH_COMPLETE_ID),
|
||||
"OSTREE_REMOTE=%s", pull_data->remote_name,
|
||||
"OSTREE_GPG=%s", verify_state,
|
||||
"OSTREE_SIGN=%s", sign_verify_state,
|
||||
"OSTREE_GPG=%s", gpg_verify_state,
|
||||
"OSTREE_SECONDS=%u", n_seconds,
|
||||
"OSTREE_XFER_SIZE=%s", formatted_xferred,
|
||||
NULL);
|
||||
@@ -4719,6 +4595,13 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
if (!ostree_repo_mark_commit_partial (pull_data->repo, commit, FALSE, error))
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* and finally any parent commits we might also have pulled because of depth>0 */
|
||||
GLNX_HASH_TABLE_FOREACH (pull_data->commit_to_depth, const char*, commit)
|
||||
{
|
||||
if (!ostree_repo_mark_commit_partial (pull_data->repo, commit, FALSE, error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
@@ -4746,6 +4629,8 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_free (pull_data->remote_refspec_name);
|
||||
g_free (pull_data->remote_name);
|
||||
g_free (pull_data->append_user_agent);
|
||||
g_clear_pointer (&pull_data->signapi_commit_verifiers, (GDestroyNotify) g_ptr_array_unref);
|
||||
g_clear_pointer (&pull_data->signapi_summary_verifiers, (GDestroyNotify) g_ptr_array_unref);
|
||||
g_clear_pointer (&pull_data->meta_mirrorlist, (GDestroyNotify) g_ptr_array_unref);
|
||||
g_clear_pointer (&pull_data->content_mirrorlist, (GDestroyNotify) g_ptr_array_unref);
|
||||
g_clear_pointer (&pull_data->summary_data, (GDestroyNotify) g_bytes_unref);
|
||||
@@ -4758,7 +4643,9 @@ ostree_repo_pull_with_options (OstreeRepo *self,
|
||||
g_clear_pointer (&pull_data->fetched_detached_metadata, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->summary_deltas_checksums, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->ref_original_commits, (GDestroyNotify) g_hash_table_unref);
|
||||
g_free (pull_data->timestamp_check_from_rev);
|
||||
g_clear_pointer (&pull_data->verified_commits, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->signapi_verified_commits, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->ref_keyring_map, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->requested_content, (GDestroyNotify) g_hash_table_unref);
|
||||
g_clear_pointer (&pull_data->requested_fallback_content, (GDestroyNotify) g_hash_table_unref);
|
||||
@@ -6016,6 +5903,8 @@ ostree_repo_pull_from_remotes_async (OstreeRepo *self,
|
||||
g_variant_dict_insert (&local_options_dict, "gpg-verify", "b", FALSE);
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
g_variant_dict_insert (&local_options_dict, "gpg-verify-summary", "b", FALSE);
|
||||
g_variant_dict_insert (&local_options_dict, "sign-verify", "b", FALSE);
|
||||
g_variant_dict_insert (&local_options_dict, "sign-verify-summary", "b", FALSE);
|
||||
g_variant_dict_insert (&local_options_dict, "inherit-transaction", "b", TRUE);
|
||||
if (result->remote->refspec_name != NULL)
|
||||
g_variant_dict_insert (&local_options_dict, "override-remote-name", "s", result->remote->refspec_name);
|
||||
@@ -6162,9 +6051,8 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self,
|
||||
g_autofree char *metalink_url_string = NULL;
|
||||
g_autoptr(GBytes) summary = NULL;
|
||||
g_autoptr(GBytes) signatures = NULL;
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
gboolean gpg_verify_summary;
|
||||
#endif
|
||||
g_autoptr(GPtrArray) signapi_summary_verifiers = NULL;
|
||||
gboolean ret = FALSE;
|
||||
gboolean summary_is_from_cache;
|
||||
|
||||
@@ -6186,37 +6074,73 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self,
|
||||
error))
|
||||
goto out;
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (!ostree_repo_remote_get_gpg_verify_summary (self, name, &gpg_verify_summary, error))
|
||||
goto out;
|
||||
|
||||
if (gpg_verify_summary && summary == NULL)
|
||||
if (gpg_verify_summary)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"GPG verification enabled, but no summary found (check that the configured URL in remote config is correct)");
|
||||
goto out;
|
||||
if (summary == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"GPG verification enabled, but no summary found (check that the configured URL in remote config is correct)");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (signatures == NULL)
|
||||
{
|
||||
g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE,
|
||||
"GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify any summary signatures. */
|
||||
if (summary != NULL && signatures != NULL)
|
||||
{
|
||||
g_autoptr(OstreeGpgVerifyResult) result = NULL;
|
||||
|
||||
result = ostree_repo_verify_summary (self,
|
||||
name,
|
||||
summary,
|
||||
signatures,
|
||||
cancellable,
|
||||
error);
|
||||
if (!ostree_gpg_verify_result_require_valid_signature (result, error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpg_verify_summary && signatures == NULL)
|
||||
{
|
||||
g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE,
|
||||
"GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)");
|
||||
goto out;
|
||||
}
|
||||
if (!_signapi_init_for_remote (self, name, NULL,
|
||||
&signapi_summary_verifiers,
|
||||
error))
|
||||
goto out;
|
||||
|
||||
/* Verify any summary signatures. */
|
||||
if (gpg_verify_summary && summary != NULL && signatures != NULL)
|
||||
if (signapi_summary_verifiers)
|
||||
{
|
||||
g_autoptr(OstreeGpgVerifyResult) result = NULL;
|
||||
if (summary == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"Signature verification enabled, but no summary found (check that the configured URL in remote config is correct)");
|
||||
goto out;
|
||||
}
|
||||
|
||||
result = ostree_repo_verify_summary (self,
|
||||
name,
|
||||
summary,
|
||||
signatures,
|
||||
cancellable,
|
||||
error);
|
||||
if (!ostree_gpg_verify_result_require_valid_signature (result, error))
|
||||
goto out;
|
||||
if (signatures == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"Signature verification enabled, but no summary signatures found (use sign-verify-summary=false in remote config to disable)");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify any summary signatures. */
|
||||
if (summary != NULL && signatures != NULL)
|
||||
{
|
||||
g_autoptr(GVariant) sig_variant = NULL;
|
||||
|
||||
sig_variant = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT,
|
||||
signatures, FALSE);
|
||||
|
||||
if (!_sign_verify_for_remote (signapi_summary_verifiers, summary, sig_variant, NULL, error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (!summary_is_from_cache && summary && signatures)
|
||||
@@ -6240,10 +6164,6 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
g_message ("%s: GPG feature is disabled in a build time", __FUNCTION__);
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
if (out_summary != NULL)
|
||||
*out_summary = g_steal_pointer (&summary);
|
||||
|
||||
|
||||
@@ -1346,7 +1346,6 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
OstreeStaticDeltaBuilder builder = { 0, };
|
||||
guint i;
|
||||
guint min_fallback_size;
|
||||
@@ -1365,8 +1364,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
g_autoptr(GVariant) detached = NULL;
|
||||
gboolean inline_parts;
|
||||
guint endianness = G_BYTE_ORDER;
|
||||
builder.parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref);
|
||||
builder.fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
|
||||
g_autoptr(GPtrArray) builder_parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref);
|
||||
g_autoptr(GPtrArray) builder_fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
|
||||
g_auto(GLnxTmpfile) descriptor_tmpf = { 0, };
|
||||
g_autoptr(OtVariantBuilder) descriptor_builder = NULL;
|
||||
|
||||
@@ -1385,6 +1384,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
g_return_val_if_fail (endianness == G_BIG_ENDIAN || endianness == G_LITTLE_ENDIAN, FALSE);
|
||||
|
||||
builder.swap_endian = endianness != G_BYTE_ORDER;
|
||||
builder.parts = builder_parts;
|
||||
builder.fallback_objects = builder_fallback_objects;
|
||||
|
||||
{ gboolean use_bsdiff;
|
||||
if (!g_variant_lookup (params, "bsdiff-enabled", "b", &use_bsdiff))
|
||||
@@ -1408,7 +1409,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
|
||||
if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, to,
|
||||
&to_commit, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
builder.delta_opts = delta_opts;
|
||||
|
||||
@@ -1419,7 +1420,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
descriptor_name = g_strdup (glnx_basename (opt_filename));
|
||||
|
||||
if (!glnx_opendirat (AT_FDCWD, dn, TRUE, &descriptor_dfd, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1428,9 +1429,9 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
const char *dn = dirname (dnbuf);
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, dn, DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
if (!glnx_opendirat (self->repo_dir_fd, dn, TRUE, &descriptor_dfd, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
descriptor_name = g_strdup (basename (descriptor_relpath));
|
||||
}
|
||||
@@ -1439,17 +1440,17 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
/* Ignore optimization flags */
|
||||
if (!generate_delta_lowlatency (self, from, to, delta_opts, &builder,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC,
|
||||
&descriptor_tmpf, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
descriptor_builder = ot_variant_builder_new (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), descriptor_tmpf.fd);
|
||||
|
||||
/* Open the metadata dict */
|
||||
if (!ot_variant_builder_open (descriptor_builder, G_VARIANT_TYPE ("a{sv}"), error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
/* NOTE: Add user-supplied metadata first. This is used by at least
|
||||
* flatpak as a way to provide MIME content sniffing, since the
|
||||
@@ -1464,7 +1465,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
while ((item = g_variant_iter_next_value (&iter)))
|
||||
{
|
||||
if (!ot_variant_builder_add_value (descriptor_builder, item, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
g_variant_unref (item);
|
||||
}
|
||||
}
|
||||
@@ -1483,7 +1484,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
if (!ot_variant_builder_add (descriptor_builder, error, "{sv}", "ostree.endianness", g_variant_new_byte (endianness_char)))
|
||||
goto out;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
part_headers = g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT));
|
||||
@@ -1504,21 +1505,18 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
!ot_variant_builder_add_from_fd (descriptor_builder, G_VARIANT_TYPE ("(yay)"), part_builder->part_tmpf.fd, part_builder->compressed_size, error) ||
|
||||
!ot_variant_builder_close (descriptor_builder, error) ||
|
||||
!ot_variant_builder_close (descriptor_builder, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_autofree char *partstr = g_strdup_printf ("%u", i);
|
||||
|
||||
if (fchmod (part_builder->part_tmpf.fd, 0644) < 0)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
return glnx_throw_errno_prefix (error, "fchmod");
|
||||
|
||||
if (!glnx_link_tmpfile_at (&part_builder->part_tmpf, GLNX_LINK_TMPFILE_REPLACE,
|
||||
descriptor_dfd, partstr, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_variant_builder_add_value (part_headers, part_builder->header);
|
||||
@@ -1529,21 +1527,21 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
|
||||
if (!get_fallback_headers (self, &builder, &fallback_headers,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
if (!ostree_repo_read_commit_detached_metadata (self, to, &detached, cancellable, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
if (detached)
|
||||
{
|
||||
g_autofree char *detached_key = _ostree_get_relative_static_delta_path (from, to, "commitmeta");
|
||||
if (!ot_variant_builder_add (descriptor_builder, error, "{sv}", detached_key, detached))
|
||||
goto out;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Close metadata dict */
|
||||
if (!ot_variant_builder_close (descriptor_builder, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
/* Generate OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */
|
||||
{
|
||||
@@ -1568,10 +1566,10 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
g_variant_builder_end (part_headers), error) ||
|
||||
!ot_variant_builder_add_value (descriptor_builder,
|
||||
fallback_headers, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
if (!ot_variant_builder_end (descriptor_builder, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
g_date_time_unref (now);
|
||||
}
|
||||
@@ -1589,18 +1587,11 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
}
|
||||
|
||||
if (fchmod (descriptor_tmpf.fd, 0644) < 0)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
return glnx_throw_errno_prefix (error, "fchmod");
|
||||
|
||||
if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE,
|
||||
descriptor_dfd, descriptor_name, error))
|
||||
goto out;
|
||||
return FALSE;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
g_clear_pointer (&builder.parts, g_ptr_array_unref);
|
||||
g_clear_pointer (&builder.fallback_objects, g_ptr_array_unref);
|
||||
return ret;
|
||||
return TRUE;
|
||||
}
|
||||
+125
-54
@@ -2028,17 +2028,8 @@ ostree_repo_remote_get_gpg_verify (OstreeRepo *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
return ostree_repo_get_remote_boolean_option (self, name, "gpg-verify",
|
||||
TRUE, out_gpg_verify, error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
if (out_gpg_verify != NULL)
|
||||
*out_gpg_verify = FALSE;
|
||||
return FALSE;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2060,17 +2051,8 @@ ostree_repo_remote_get_gpg_verify_summary (OstreeRepo *self,
|
||||
gboolean *out_gpg_verify_summary,
|
||||
GError **error)
|
||||
{
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
return ostree_repo_get_remote_boolean_option (self, name, "gpg-verify-summary",
|
||||
FALSE, out_gpg_verify_summary, error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
if (out_gpg_verify_summary != NULL)
|
||||
*out_gpg_verify_summary = FALSE;
|
||||
return FALSE;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2344,10 +2326,7 @@ out:
|
||||
|
||||
return ret;
|
||||
#else /* OSTREE_DISABLE_GPGME */
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
return FALSE;
|
||||
return glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
@@ -2943,6 +2922,10 @@ reload_core_config (OstreeRepo *self,
|
||||
ostree_repo_set_disable_fsync (self, TRUE);
|
||||
}
|
||||
|
||||
if (!ot_keyfile_get_boolean_with_default (self->config, "core", "per-object-fsync",
|
||||
FALSE, &self->per_object_fsync, error))
|
||||
return FALSE;
|
||||
|
||||
/* See https://github.com/ostreedev/ostree/issues/758 */
|
||||
if (!ot_keyfile_get_boolean_with_default (self->config, "core", "disable-xattrs",
|
||||
FALSE, &self->disable_xattrs, error))
|
||||
@@ -3257,6 +3240,8 @@ ostree_repo_open (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("opening repo", error);
|
||||
|
||||
struct stat stbuf;
|
||||
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
@@ -3291,10 +3276,7 @@ ostree_repo_open (OstreeRepo *self,
|
||||
g_assert (self->repodir);
|
||||
if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->repodir), TRUE,
|
||||
&self->repo_dir_fd, error))
|
||||
{
|
||||
g_prefix_error (error, "%s: ", gs_file_get_path_cached (self->repodir));
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!glnx_fstat (self->repo_dir_fd, &stbuf, error))
|
||||
@@ -4992,10 +4974,7 @@ ostree_repo_append_gpg_signature (OstreeRepo *self,
|
||||
|
||||
return TRUE;
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
return FALSE;
|
||||
return glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
@@ -5147,7 +5126,7 @@ ostree_repo_sign_commit (OstreeRepo *self,
|
||||
return TRUE;
|
||||
#else
|
||||
/* FIXME: Return false until refactoring */
|
||||
return FALSE;
|
||||
return glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
@@ -5239,10 +5218,7 @@ ostree_repo_add_gpg_signature_summary (OstreeRepo *self,
|
||||
|
||||
return TRUE;
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
return FALSE;
|
||||
return glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
@@ -5516,10 +5492,7 @@ ostree_repo_verify_commit (OstreeRepo *self,
|
||||
return TRUE;
|
||||
#else
|
||||
/* FIXME: Return false until refactoring */
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
return FALSE;
|
||||
return glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
|
||||
@@ -5554,9 +5527,7 @@ ostree_repo_verify_commit_ext (OstreeRepo *self,
|
||||
cancellable,
|
||||
error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
return NULL;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
@@ -5593,9 +5564,7 @@ ostree_repo_verify_commit_for_remote (OstreeRepo *self,
|
||||
cancellable,
|
||||
error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
return NULL;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
@@ -5645,9 +5614,7 @@ ostree_repo_gpg_verify_data (OstreeRepo *self,
|
||||
cancellable,
|
||||
error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
return NULL;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
@@ -5693,9 +5660,7 @@ ostree_repo_verify_summary (OstreeRepo *self,
|
||||
cancellable,
|
||||
error);
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"'%s': GPG feature is disabled in a build time",
|
||||
__FUNCTION__);
|
||||
glnx_throw (error, "GPG feature is disabled in a build time");
|
||||
return NULL;
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
}
|
||||
@@ -5998,7 +5963,7 @@ _ostree_repo_maybe_regenerate_summary (OstreeRepo *self,
|
||||
}
|
||||
|
||||
gboolean
|
||||
_ostree_repo_is_locked_tmpdir (const char *filename)
|
||||
_ostree_repo_has_staging_prefix (const char *filename)
|
||||
{
|
||||
return g_str_has_prefix (filename, OSTREE_REPO_TMPDIR_STAGING);
|
||||
}
|
||||
@@ -6019,7 +5984,9 @@ _ostree_repo_try_lock_tmpdir (int tmpdir_dfd,
|
||||
if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB,
|
||||
file_lock_out, &local_error))
|
||||
{
|
||||
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|
||||
/* we need to handle EACCES too in the case of POSIX locks; see F_SETLK in fcntl(2) */
|
||||
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)
|
||||
|| g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
|
||||
{
|
||||
did_lock = FALSE;
|
||||
}
|
||||
@@ -6058,7 +6025,7 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (_ostree_repo_is_locked_tmpdir (tmpdir_prefix), FALSE);
|
||||
g_return_val_if_fail (_ostree_repo_has_staging_prefix (tmpdir_prefix), FALSE);
|
||||
|
||||
/* Look for existing tmpdir (with same prefix) to reuse */
|
||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||
@@ -6280,3 +6247,107 @@ ostree_repo_get_bootloader (OstreeRepo *self)
|
||||
|
||||
return self->bootloader;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* _ostree_repo_verify_bindings:
|
||||
* @collection_id: (nullable): Locally specified collection ID for the remote
|
||||
* the @commit was retrieved from, or %NULL if none is configured
|
||||
* @ref_name: (nullable): Ref name the commit was retrieved using, or %NULL if
|
||||
* the commit was retrieved by checksum
|
||||
* @commit: Commit data to check
|
||||
* @error: Return location for a #GError, or %NULL
|
||||
*
|
||||
* Verify the ref and collection bindings.
|
||||
*
|
||||
* The ref binding is verified only if it exists. But if we have the
|
||||
* collection ID specified in the remote configuration (@collection_id is
|
||||
* non-%NULL) then the ref binding must exist, otherwise the verification will
|
||||
* fail. Parts of the verification can be skipped by passing %NULL to the
|
||||
* @ref_name parameter (in case we requested a checksum directly, without
|
||||
* looking it up from a ref).
|
||||
*
|
||||
* The collection binding is verified only when we have collection ID
|
||||
* specified in the remote configuration. If it is specified, then the
|
||||
* binding must exist and must be equal to the remote repository
|
||||
* collection ID.
|
||||
*
|
||||
* Returns: %TRUE if bindings are correct, %FALSE otherwise
|
||||
* Since: 2017.14
|
||||
*/
|
||||
gboolean
|
||||
_ostree_repo_verify_bindings (const char *collection_id,
|
||||
const char *ref_name,
|
||||
GVariant *commit,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GVariant) metadata = g_variant_get_child_value (commit, 0);
|
||||
g_autofree const char **refs = NULL;
|
||||
if (!g_variant_lookup (metadata,
|
||||
OSTREE_COMMIT_META_KEY_REF_BINDING,
|
||||
"^a&s",
|
||||
&refs))
|
||||
{
|
||||
/* Early return here - if the remote collection ID is NULL, then
|
||||
* we certainly will not verify the collection binding in the
|
||||
* commit.
|
||||
*/
|
||||
if (collection_id == NULL)
|
||||
return TRUE;
|
||||
|
||||
return glnx_throw (error,
|
||||
"Expected commit metadata to have ref "
|
||||
"binding information, found none");
|
||||
}
|
||||
|
||||
if (ref_name != NULL)
|
||||
{
|
||||
if (!g_strv_contains ((const char *const *) refs, ref_name))
|
||||
{
|
||||
g_autoptr(GString) refs_dump = g_string_new (NULL);
|
||||
const char *refs_str;
|
||||
|
||||
if (refs != NULL && (*refs) != NULL)
|
||||
{
|
||||
for (const char **iter = refs; *iter != NULL; ++iter)
|
||||
{
|
||||
const char *ref = *iter;
|
||||
|
||||
if (refs_dump->len > 0)
|
||||
g_string_append (refs_dump, ", ");
|
||||
g_string_append_printf (refs_dump, "‘%s’", ref);
|
||||
}
|
||||
|
||||
refs_str = refs_dump->str;
|
||||
}
|
||||
else
|
||||
{
|
||||
refs_str = "no refs";
|
||||
}
|
||||
|
||||
return glnx_throw (error, "Commit has no requested ref ‘%s’ "
|
||||
"in ref binding metadata (%s)",
|
||||
ref_name, refs_str);
|
||||
}
|
||||
}
|
||||
|
||||
if (collection_id != NULL)
|
||||
{
|
||||
const char *collection_id_binding;
|
||||
if (!g_variant_lookup (metadata,
|
||||
OSTREE_COMMIT_META_KEY_COLLECTION_BINDING,
|
||||
"&s",
|
||||
&collection_id_binding))
|
||||
return glnx_throw (error,
|
||||
"Expected commit metadata to have collection ID "
|
||||
"binding information, found none");
|
||||
if (!g_str_equal (collection_id_binding, collection_id))
|
||||
return glnx_throw (error,
|
||||
"Commit has collection ID ‘%s’ in collection binding "
|
||||
"metadata, while the remote it came from has "
|
||||
"collection ID ‘%s’",
|
||||
collection_id_binding, collection_id);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -691,6 +691,13 @@ _OSTREE_PUBLIC
|
||||
void ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier *modifier,
|
||||
OstreeSePolicy *sepolicy);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier *modifier,
|
||||
OstreeRepo *repo,
|
||||
const char *rev,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
void ostree_repo_commit_modifier_set_devino_cache (OstreeRepoCommitModifier *modifier,
|
||||
OstreeRepoDevInoCache *cache);
|
||||
@@ -1167,9 +1174,9 @@ void ostree_repo_commit_traverse_iter_cleanup (void *p);
|
||||
* @OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY: Do not traverse individual commit objects, only follow refs
|
||||
*/
|
||||
typedef enum {
|
||||
OSTREE_REPO_PRUNE_FLAGS_NONE,
|
||||
OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE,
|
||||
OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY
|
||||
OSTREE_REPO_PRUNE_FLAGS_NONE = 0,
|
||||
OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE = (1 << 0),
|
||||
OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY = (1 << 1),
|
||||
} OstreeRepoPruneFlags;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <libglnx.h>
|
||||
#include "ostree-sign-dummy.h"
|
||||
#include <string.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "OSTreeSign"
|
||||
|
||||
#define OSTREE_SIGN_DUMMY_NAME "dummy"
|
||||
|
||||
#define OSTREE_SIGN_METADATA_DUMMY_KEY "ostree.sign.dummy"
|
||||
#define OSTREE_SIGN_METADATA_DUMMY_TYPE "aay"
|
||||
|
||||
struct _OstreeSignDummy
|
||||
{
|
||||
GObject parent;
|
||||
gchar *sk_ascii;
|
||||
gchar *pk_ascii;
|
||||
};
|
||||
|
||||
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSignDummy, g_object_unref)
|
||||
#endif
|
||||
|
||||
static void
|
||||
ostree_sign_dummy_iface_init (OstreeSignInterface *self);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (OstreeSignDummy, _ostree_sign_dummy, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_SIGN, ostree_sign_dummy_iface_init));
|
||||
|
||||
static gboolean
|
||||
check_dummy_sign_enabled (GError **error)
|
||||
{
|
||||
if (g_strcmp0 (g_getenv ("OSTREE_DUMMY_SIGN_ENABLED"), "1") != 0)
|
||||
return glnx_throw (error, "dummy signature type is only for ostree testing");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
ostree_sign_dummy_iface_init (OstreeSignInterface *self)
|
||||
{
|
||||
|
||||
self->get_name = ostree_sign_dummy_get_name;
|
||||
self->data = ostree_sign_dummy_data;
|
||||
self->data_verify = ostree_sign_dummy_data_verify;
|
||||
self->metadata_key = ostree_sign_dummy_metadata_key;
|
||||
self->metadata_format = ostree_sign_dummy_metadata_format;
|
||||
self->set_sk = ostree_sign_dummy_set_sk;
|
||||
self->set_pk = ostree_sign_dummy_set_pk;
|
||||
/* Implementation for dummy engine just load the single public key */
|
||||
self->add_pk = ostree_sign_dummy_set_pk;
|
||||
}
|
||||
|
||||
static void
|
||||
_ostree_sign_dummy_class_init (OstreeSignDummyClass *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
_ostree_sign_dummy_init (OstreeSignDummy *self)
|
||||
{
|
||||
|
||||
self->sk_ascii = NULL;
|
||||
self->pk_ascii = NULL;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_dummy_set_sk (OstreeSign *self, GVariant *key, GError **error)
|
||||
{
|
||||
if (!check_dummy_sign_enabled (error))
|
||||
return FALSE;
|
||||
|
||||
OstreeSignDummy *sign = _ostree_sign_dummy_get_instance_private(OSTREE_SIGN_DUMMY(self));
|
||||
|
||||
g_free(sign->sk_ascii);
|
||||
|
||||
sign->sk_ascii = g_variant_dup_string (key, 0);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_dummy_set_pk (OstreeSign *self, GVariant *key, GError **error)
|
||||
{
|
||||
OstreeSignDummy *sign = _ostree_sign_dummy_get_instance_private(OSTREE_SIGN_DUMMY(self));
|
||||
|
||||
g_free(sign->pk_ascii);
|
||||
|
||||
sign->pk_ascii = g_variant_dup_string (key, 0);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_dummy_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!check_dummy_sign_enabled (error))
|
||||
return FALSE;
|
||||
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
OstreeSignDummy *sign = _ostree_sign_dummy_get_instance_private(OSTREE_SIGN_DUMMY(self));
|
||||
|
||||
*signature = g_bytes_new (sign->sk_ascii, strlen(sign->sk_ascii));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_dummy_get_name (OstreeSign *self)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
return OSTREE_SIGN_DUMMY_NAME;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_dummy_metadata_key (OstreeSign *self)
|
||||
{
|
||||
|
||||
return OSTREE_SIGN_METADATA_DUMMY_KEY;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_dummy_metadata_format (OstreeSign *self)
|
||||
{
|
||||
|
||||
return OSTREE_SIGN_METADATA_DUMMY_TYPE;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_dummy_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error)
|
||||
{
|
||||
if (!check_dummy_sign_enabled (error))
|
||||
return FALSE;
|
||||
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
|
||||
OstreeSignDummy *sign = _ostree_sign_dummy_get_instance_private(OSTREE_SIGN_DUMMY(self));
|
||||
|
||||
if (signatures == NULL)
|
||||
return glnx_throw (error, "signature: dummy: commit have no signatures of my type");
|
||||
|
||||
if (!g_variant_is_of_type (signatures, (GVariantType *) OSTREE_SIGN_METADATA_DUMMY_TYPE))
|
||||
return glnx_throw (error, "signature: dummy: wrong type passed for verification");
|
||||
|
||||
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);
|
||||
|
||||
gsize sign_size = 0;
|
||||
g_bytes_get_data (signature, &sign_size);
|
||||
g_autofree gchar *sign_ascii = g_strndup(g_bytes_get_data (signature, NULL), sign_size);
|
||||
g_debug("Read signature %d: %s", (gint)i, sign_ascii);
|
||||
g_debug("Stored signature %d: %s", (gint)i, sign->pk_ascii);
|
||||
|
||||
if (!g_strcmp0(sign_ascii, sign->pk_ascii))
|
||||
{
|
||||
if (out_success_message)
|
||||
*out_success_message = g_strdup ("dummy: Signature verified");
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
return glnx_throw (error, "signature: dummy: incorrect signature %" G_GSIZE_FORMAT, i);
|
||||
}
|
||||
|
||||
return glnx_throw (error, "signature: dummy: no signatures");
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors:
|
||||
* - Denis Pynkin (d4s) <denis.pynkin@collabora.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ostree-sign.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define OSTREE_TYPE_SIGN_DUMMY (_ostree_sign_dummy_get_type ())
|
||||
|
||||
GType _ostree_sign_dummy_get_type (void);
|
||||
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
typedef struct _OstreeSignDummy OstreeSignDummy;
|
||||
typedef struct { GObjectClass parent_class; } OstreeSignDummyClass;
|
||||
|
||||
static inline OstreeSignDummy *OSTREE_SIGN_DUMMY (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, _ostree_sign_dummy_get_type (), OstreeSignDummy); }
|
||||
static inline gboolean OSTREE_IS_SIGN_DUMMY (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, _ostree_sign_dummy_get_type ()); }
|
||||
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
/* Have to use glib-2.44 for this
|
||||
_OSTREE_PUBLIC
|
||||
G_DECLARE_FINAL_TYPE (OstreeSignDummy,
|
||||
ostree_sign_dummy,
|
||||
OSTREE,
|
||||
SIGN_DUMMY,
|
||||
GObject)
|
||||
*/
|
||||
|
||||
const gchar * ostree_sign_dummy_get_name (OstreeSign *self);
|
||||
|
||||
gboolean ostree_sign_dummy_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_dummy_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **success_message,
|
||||
GError **error);
|
||||
|
||||
const gchar * ostree_sign_dummy_metadata_key (OstreeSign *self);
|
||||
const gchar * ostree_sign_dummy_metadata_format (OstreeSign *self);
|
||||
|
||||
gboolean ostree_sign_dummy_set_sk (OstreeSign *self, GVariant *key, GError **error);
|
||||
gboolean ostree_sign_dummy_set_pk (OstreeSign *self, GVariant *key, GError **error);
|
||||
gboolean ostree_sign_dummy_add_pk (OstreeSign *self, GVariant *key, GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -0,0 +1,672 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors:
|
||||
* - Denis Pynkin (d4s) <denis.pynkin@collabora.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <libglnx.h>
|
||||
#include "ostree-sign-ed25519.h"
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
#include <sodium.h>
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "OSTreeSign"
|
||||
|
||||
#define OSTREE_SIGN_ED25519_NAME "ed25519"
|
||||
|
||||
#define OSTREE_SIGN_METADATA_ED25519_KEY "ostree.sign.ed25519"
|
||||
#define OSTREE_SIGN_METADATA_ED25519_TYPE "aay"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ED25519_OK,
|
||||
ED25519_NOT_SUPPORTED,
|
||||
ED25519_FAILED_INITIALIZATION
|
||||
} ed25519_state;
|
||||
|
||||
struct _OstreeSignEd25519
|
||||
{
|
||||
GObject parent;
|
||||
ed25519_state state;
|
||||
guchar *secret_key;
|
||||
GList *public_keys;
|
||||
GList *revoked_keys;
|
||||
};
|
||||
|
||||
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSignEd25519, g_object_unref)
|
||||
#endif
|
||||
|
||||
static void
|
||||
ostree_sign_ed25519_iface_init (OstreeSignInterface *self);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (OstreeSignEd25519, _ostree_sign_ed25519, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (OSTREE_TYPE_SIGN, ostree_sign_ed25519_iface_init));
|
||||
|
||||
static void
|
||||
ostree_sign_ed25519_iface_init (OstreeSignInterface *self)
|
||||
{
|
||||
|
||||
self->data = ostree_sign_ed25519_data;
|
||||
self->data_verify = ostree_sign_ed25519_data_verify;
|
||||
self->get_name = ostree_sign_ed25519_get_name;
|
||||
self->metadata_key = ostree_sign_ed25519_metadata_key;
|
||||
self->metadata_format = ostree_sign_ed25519_metadata_format;
|
||||
self->clear_keys = ostree_sign_ed25519_clear_keys;
|
||||
self->set_sk = ostree_sign_ed25519_set_sk;
|
||||
self->set_pk = ostree_sign_ed25519_set_pk;
|
||||
self->add_pk = ostree_sign_ed25519_add_pk;
|
||||
self->load_pk = ostree_sign_ed25519_load_pk;
|
||||
}
|
||||
|
||||
static void
|
||||
_ostree_sign_ed25519_class_init (OstreeSignEd25519Class *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
_ostree_sign_ed25519_init (OstreeSignEd25519 *self)
|
||||
{
|
||||
|
||||
self->state = ED25519_OK;
|
||||
self->secret_key = NULL;
|
||||
self->public_keys = NULL;
|
||||
self->revoked_keys = NULL;
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
if (sodium_init() < 0)
|
||||
self->state = ED25519_FAILED_INITIALIZATION;
|
||||
#else
|
||||
self->state = ED25519_NOT_SUPPORTED;
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ostree_sign_ed25519_is_initialized (OstreeSignEd25519 *self, GError **error)
|
||||
{
|
||||
switch (self->state)
|
||||
{
|
||||
case ED25519_OK:
|
||||
break;
|
||||
case ED25519_NOT_SUPPORTED:
|
||||
return glnx_throw(error, "ed25519: engine is not supported");
|
||||
case ED25519_FAILED_INITIALIZATION:
|
||||
return glnx_throw(error, "ed25519: libsodium library isn't initialized properly");
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_ed25519_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
guchar *sig = NULL;
|
||||
#endif
|
||||
|
||||
if (!_ostree_sign_ed25519_is_initialized (sign, error))
|
||||
return FALSE;
|
||||
|
||||
if (sign->secret_key == NULL)
|
||||
return glnx_throw (error, "Not able to sign: secret key is not set");
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
unsigned long long sig_size = 0;
|
||||
|
||||
sig = g_malloc0(crypto_sign_BYTES);
|
||||
|
||||
if (crypto_sign_detached (sig,
|
||||
&sig_size,
|
||||
g_bytes_get_data (data, NULL),
|
||||
g_bytes_get_size (data),
|
||||
sign->secret_key))
|
||||
{
|
||||
return glnx_throw (error, "Not able to sign: fail to sign the object");
|
||||
}
|
||||
|
||||
*signature = g_bytes_new_take (sig, sig_size);
|
||||
return TRUE;
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
static gint
|
||||
_compare_ed25519_keys(gconstpointer a, gconstpointer b) {
|
||||
return memcmp (a, b, crypto_sign_PUBLICKEYBYTES);
|
||||
}
|
||||
#endif
|
||||
|
||||
gboolean ostree_sign_ed25519_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
if (!_ostree_sign_ed25519_is_initialized (sign, error))
|
||||
return FALSE;
|
||||
|
||||
if (signatures == NULL)
|
||||
return glnx_throw (error, "ed25519: commit have no signatures of my type");
|
||||
|
||||
if (!g_variant_is_of_type (signatures, (GVariantType *) OSTREE_SIGN_METADATA_ED25519_TYPE))
|
||||
return glnx_throw (error, "ed25519: wrong type passed for verification");
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
/* If no keys pre-loaded then,
|
||||
* try to load public keys from storage(s) */
|
||||
if (sign->public_keys == NULL)
|
||||
{
|
||||
g_autoptr (GVariantBuilder) builder = NULL;
|
||||
g_autoptr (GVariant) options = NULL;
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
||||
options = g_variant_builder_end (builder);
|
||||
|
||||
if (!ostree_sign_ed25519_load_pk (self, options, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_debug ("verify: data hash = 0x%x", g_bytes_hash(data));
|
||||
|
||||
g_autoptr(GString) invalid_signatures = NULL;
|
||||
guint n_invalid_signatures = 0;
|
||||
|
||||
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);
|
||||
|
||||
g_autofree char * hex = g_malloc0 (crypto_sign_PUBLICKEYBYTES*2 + 1);
|
||||
|
||||
g_debug("Read signature %d: %s", (gint)i, g_variant_print(child, TRUE));
|
||||
|
||||
for (GList *public_key = sign->public_keys;
|
||||
public_key != NULL;
|
||||
public_key = public_key->next)
|
||||
{
|
||||
|
||||
/* TODO: use non-list for tons of revoked keys? */
|
||||
if (g_list_find_custom (sign->revoked_keys, public_key->data, _compare_ed25519_keys) != NULL)
|
||||
{
|
||||
g_debug("Skip revoked key '%s'",
|
||||
sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, public_key->data, crypto_sign_PUBLICKEYBYTES));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (crypto_sign_verify_detached ((guchar *) g_variant_get_data (child),
|
||||
g_bytes_get_data (data, NULL),
|
||||
g_bytes_get_size (data),
|
||||
public_key->data) != 0)
|
||||
{
|
||||
/* Incorrect signature! */
|
||||
if (invalid_signatures == NULL)
|
||||
invalid_signatures = g_string_new ("");
|
||||
else
|
||||
g_string_append (invalid_signatures, "; ");
|
||||
n_invalid_signatures++;
|
||||
g_string_append_printf (invalid_signatures, "key '%s'",
|
||||
sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, public_key->data, crypto_sign_PUBLICKEYBYTES));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (out_success_message)
|
||||
{
|
||||
*out_success_message =
|
||||
g_strdup_printf ("ed25519: Signature verified successfully with key '%s'",
|
||||
sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, public_key->data, crypto_sign_PUBLICKEYBYTES));
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid_signatures)
|
||||
{
|
||||
g_assert_cmpuint (n_invalid_signatures, >, 0);
|
||||
/* The test suite has a key ring with 100 keys. This seems insane, let's
|
||||
* cap a reasonable error message at 3.
|
||||
*/
|
||||
if (n_invalid_signatures > 3)
|
||||
return glnx_throw (error, "ed25519: Signature couldn't be verified; tried %u keys", n_invalid_signatures);
|
||||
return glnx_throw (error, "ed25519: Signature couldn't be verified with: %s", invalid_signatures->str);
|
||||
}
|
||||
return glnx_throw (error, "ed25519: no signatures found");
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_ed25519_get_name (OstreeSign *self)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
return OSTREE_SIGN_ED25519_NAME;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_ed25519_metadata_key (OstreeSign *self)
|
||||
{
|
||||
|
||||
return OSTREE_SIGN_METADATA_ED25519_KEY;
|
||||
}
|
||||
|
||||
const gchar * ostree_sign_ed25519_metadata_format (OstreeSign *self)
|
||||
{
|
||||
|
||||
return OSTREE_SIGN_METADATA_ED25519_TYPE;
|
||||
}
|
||||
|
||||
gboolean ostree_sign_ed25519_clear_keys (OstreeSign *self,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
if (!_ostree_sign_ed25519_is_initialized (sign, error))
|
||||
return FALSE;
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
/* Clear secret key */
|
||||
if (sign->secret_key != NULL)
|
||||
{
|
||||
memset (sign->secret_key, 0, crypto_sign_SECRETKEYBYTES);
|
||||
g_free (sign->secret_key);
|
||||
sign->secret_key = NULL;
|
||||
}
|
||||
|
||||
/* Clear already loaded trusted keys */
|
||||
if (sign->public_keys != NULL)
|
||||
{
|
||||
g_list_free_full (sign->public_keys, g_free);
|
||||
sign->public_keys = NULL;
|
||||
}
|
||||
|
||||
/* Clear already loaded revoked keys */
|
||||
if (sign->revoked_keys != NULL)
|
||||
{
|
||||
g_list_free_full (sign->revoked_keys, g_free);
|
||||
sign->revoked_keys = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Support 2 representations:
|
||||
* base64 ascii -- secret key is passed as string
|
||||
* raw key -- key is passed as bytes array
|
||||
* */
|
||||
gboolean ostree_sign_ed25519_set_sk (OstreeSign *self,
|
||||
GVariant *secret_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
|
||||
if (!ostree_sign_ed25519_clear_keys (self, error))
|
||||
return FALSE;
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
gsize n_elements = 0;
|
||||
|
||||
if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_STRING))
|
||||
{
|
||||
const gchar *sk_ascii = g_variant_get_string (secret_key, NULL);
|
||||
sign->secret_key = g_base64_decode (sk_ascii, &n_elements);
|
||||
}
|
||||
else if (g_variant_is_of_type (secret_key, G_VARIANT_TYPE_BYTESTRING))
|
||||
{
|
||||
sign->secret_key = (guchar *) g_variant_get_fixed_array (secret_key, &n_elements, sizeof(guchar));
|
||||
}
|
||||
else
|
||||
{
|
||||
return glnx_throw (error, "Unknown ed25519 secret key type");
|
||||
}
|
||||
|
||||
if (n_elements != crypto_sign_SECRETKEYBYTES)
|
||||
return glnx_throw (error, "Incorrect ed25519 secret key");
|
||||
|
||||
return TRUE;
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Support 2 representations:
|
||||
* base64 ascii -- public key is passed as string
|
||||
* raw key -- key is passed as bytes array
|
||||
* */
|
||||
gboolean ostree_sign_ed25519_set_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
if (!ostree_sign_ed25519_clear_keys (self, error))
|
||||
return FALSE;
|
||||
|
||||
return ostree_sign_ed25519_add_pk (self, public_key, error);
|
||||
}
|
||||
|
||||
/* Support 2 representations:
|
||||
* base64 ascii -- public key is passed as string
|
||||
* raw key -- key is passed as bytes array
|
||||
* */
|
||||
gboolean ostree_sign_ed25519_add_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
if (!_ostree_sign_ed25519_is_initialized (sign, error))
|
||||
return FALSE;
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
gpointer key = NULL;
|
||||
gsize n_elements = 0;
|
||||
|
||||
if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_STRING))
|
||||
{
|
||||
const gchar *pk_ascii = g_variant_get_string (public_key, NULL);
|
||||
key = g_base64_decode (pk_ascii, &n_elements);
|
||||
}
|
||||
else if (g_variant_is_of_type (public_key, G_VARIANT_TYPE_BYTESTRING))
|
||||
{
|
||||
key = (gpointer) g_variant_get_fixed_array (public_key, &n_elements, sizeof(guchar));
|
||||
}
|
||||
else
|
||||
{
|
||||
return glnx_throw (error, "Unknown ed25519 public key type");
|
||||
}
|
||||
|
||||
if (n_elements != crypto_sign_PUBLICKEYBYTES)
|
||||
return glnx_throw (error, "Incorrect ed25519 public key");
|
||||
|
||||
g_autofree char *hex = g_malloc0 (crypto_sign_PUBLICKEYBYTES*2 + 1);
|
||||
g_debug ("Read ed25519 public key = %s", sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, key, n_elements));
|
||||
|
||||
if (g_list_find_custom (sign->public_keys, key, _compare_ed25519_keys) == NULL)
|
||||
{
|
||||
gpointer newkey = g_memdup (key, n_elements);
|
||||
sign->public_keys = g_list_prepend (sign->public_keys, newkey);
|
||||
}
|
||||
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
/* Add revoked public key */
|
||||
static gboolean
|
||||
_ed25519_add_revoked (OstreeSign *self,
|
||||
GVariant *revoked_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
if (!g_variant_is_of_type (revoked_key, G_VARIANT_TYPE_STRING))
|
||||
return glnx_throw (error, "Unknown ed25519 revoked key type");
|
||||
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
|
||||
const gchar *rk_ascii = g_variant_get_string (revoked_key, NULL);
|
||||
gsize n_elements = 0;
|
||||
gpointer key = g_base64_decode (rk_ascii, &n_elements);
|
||||
|
||||
if (n_elements != crypto_sign_PUBLICKEYBYTES)
|
||||
{
|
||||
return glnx_throw (error, "Incorrect ed25519 revoked key");
|
||||
}
|
||||
|
||||
g_autofree char * hex = g_malloc0 (crypto_sign_PUBLICKEYBYTES*2 + 1);
|
||||
g_debug ("Read ed25519 revoked key = %s", sodium_bin2hex (hex, crypto_sign_PUBLICKEYBYTES*2+1, key, n_elements));
|
||||
|
||||
if (g_list_find_custom (sign->revoked_keys, key, _compare_ed25519_keys) == NULL)
|
||||
{
|
||||
gpointer newkey = g_memdup (key, n_elements);
|
||||
sign->revoked_keys = g_list_prepend (sign->revoked_keys, newkey);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
|
||||
|
||||
static gboolean
|
||||
_load_pk_from_stream (OstreeSign *self,
|
||||
GDataInputStream *key_data_in,
|
||||
gboolean trusted,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (key_data_in, FALSE);
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
gboolean ret = FALSE;
|
||||
|
||||
/* Use simple file format with just a list of base64 public keys per line */
|
||||
while (TRUE)
|
||||
{
|
||||
gsize len = 0;
|
||||
g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error);
|
||||
g_autoptr (GVariant) pk = NULL;
|
||||
gboolean added = FALSE;
|
||||
|
||||
if (*error != NULL)
|
||||
return FALSE;
|
||||
|
||||
if (line == NULL)
|
||||
return ret;
|
||||
|
||||
/* Read the key itself */
|
||||
/* base64 encoded key */
|
||||
pk = g_variant_new_string (line);
|
||||
|
||||
if (trusted)
|
||||
added = ostree_sign_ed25519_add_pk (self, pk, error);
|
||||
else
|
||||
added = _ed25519_add_revoked (self, pk, error);
|
||||
|
||||
g_debug ("%s %s key: %s",
|
||||
added ? "Added" : "Invalid",
|
||||
trusted ? "public" : "revoked",
|
||||
line);
|
||||
|
||||
/* Mark what we load at least one key */
|
||||
if (added)
|
||||
ret = TRUE;
|
||||
}
|
||||
#endif /* HAVE_LIBSODIUM */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_load_pk_from_file (OstreeSign *self,
|
||||
const gchar *filename,
|
||||
gboolean trusted,
|
||||
GError **error)
|
||||
{
|
||||
g_debug ("Processing file '%s'", filename);
|
||||
|
||||
g_autoptr (GFile) keyfile = NULL;
|
||||
g_autoptr (GFileInputStream) key_stream_in = NULL;
|
||||
g_autoptr (GDataInputStream) key_data_in = NULL;
|
||||
|
||||
if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
g_debug ("Can't open file '%s' with public keys", filename);
|
||||
return glnx_throw (error, "File object '%s' is not a regular file", filename);
|
||||
}
|
||||
|
||||
keyfile = g_file_new_for_path (filename);
|
||||
key_stream_in = g_file_read (keyfile, NULL, error);
|
||||
if (key_stream_in == NULL)
|
||||
return FALSE;
|
||||
|
||||
key_data_in = g_data_input_stream_new (G_INPUT_STREAM(key_stream_in));
|
||||
g_assert (key_data_in != NULL);
|
||||
|
||||
if (!_load_pk_from_stream (self, key_data_in, trusted, error))
|
||||
{
|
||||
if (error == NULL || *error == NULL)
|
||||
return glnx_throw (error,
|
||||
"signature: ed25519: no valid keys in file '%s'",
|
||||
filename);
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ed25519_load_pk (OstreeSign *self,
|
||||
GVariant *options,
|
||||
gboolean trusted,
|
||||
GError **error)
|
||||
{
|
||||
|
||||
gboolean ret = FALSE;
|
||||
const gchar *custom_dir = NULL;
|
||||
|
||||
g_autoptr (GPtrArray) base_dirs = g_ptr_array_new_with_free_func (g_free);
|
||||
g_autoptr (GPtrArray) ed25519_files = g_ptr_array_new_with_free_func (g_free);
|
||||
|
||||
if (g_variant_lookup (options, "basedir", "&s", &custom_dir))
|
||||
{
|
||||
/* Add custom directory */
|
||||
g_ptr_array_add (base_dirs, g_strdup (custom_dir));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Default paths where to find files with public keys */
|
||||
g_ptr_array_add (base_dirs, g_strdup ("/etc/ostree"));
|
||||
g_ptr_array_add (base_dirs, g_strdup (DATADIR "/ostree"));
|
||||
}
|
||||
|
||||
/* Scan all well-known directories and construct the list with file names to scan keys */
|
||||
for (gint i=0; i < base_dirs->len; i++)
|
||||
{
|
||||
gchar *base_name = NULL;
|
||||
g_autofree gchar *base_dir = NULL;
|
||||
g_autoptr (GDir) dir = NULL;
|
||||
|
||||
base_name = g_build_filename ((gchar *)g_ptr_array_index (base_dirs, i),
|
||||
trusted ? "trusted.ed25519" : "revoked.ed25519",
|
||||
NULL);
|
||||
|
||||
g_debug ("Check ed25519 keys from file: %s", base_name);
|
||||
g_ptr_array_add (ed25519_files, base_name);
|
||||
|
||||
base_dir = g_strconcat (base_name, ".d", NULL);
|
||||
dir = g_dir_open (base_dir, 0, error);
|
||||
if (dir == NULL)
|
||||
{
|
||||
g_clear_error (error);
|
||||
continue;
|
||||
}
|
||||
const gchar *entry = NULL;
|
||||
while ((entry = g_dir_read_name (dir)) != NULL)
|
||||
{
|
||||
gchar *filename = g_build_filename (base_dir, entry, NULL);
|
||||
g_debug ("Check ed25519 keys from file: %s", filename);
|
||||
g_ptr_array_add (ed25519_files, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan all well-known files */
|
||||
for (gint i=0; i < ed25519_files->len; i++)
|
||||
{
|
||||
if (!_load_pk_from_file (self, (gchar *)g_ptr_array_index (ed25519_files, i), trusted, error))
|
||||
{
|
||||
g_debug ("Problem with loading ed25519 %s keys from `%s`",
|
||||
trusted ? "public" : "revoked",
|
||||
(gchar *)g_ptr_array_index (ed25519_files, i));
|
||||
g_clear_error(error);
|
||||
}
|
||||
else
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
if (!ret && (error == NULL || *error == NULL))
|
||||
return glnx_throw (error, "signature: ed25519: no keys loaded");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* options argument should be a{sv}:
|
||||
* - filename -- single file to use to load keys from;
|
||||
* - basedir -- directory containing subdirectories
|
||||
* 'trusted.ed25519.d' and 'revoked.ed25519.d' with appropriate
|
||||
* public keys. Used for testing and re-definition of system-wide
|
||||
* directories if defaults are not suitable for any reason.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_ed25519_load_pk (OstreeSign *self,
|
||||
GVariant *options,
|
||||
GError **error)
|
||||
{
|
||||
|
||||
const gchar *filename = NULL;
|
||||
|
||||
OstreeSignEd25519 *sign = _ostree_sign_ed25519_get_instance_private(OSTREE_SIGN_ED25519(self));
|
||||
if (!_ostree_sign_ed25519_is_initialized (sign, error))
|
||||
return FALSE;
|
||||
|
||||
/* Read keys only from single file provided */
|
||||
if (g_variant_lookup (options, "filename", "&s", &filename))
|
||||
return _load_pk_from_file (self, filename, TRUE, error);
|
||||
|
||||
/* Load public keys from well-known directories and files */
|
||||
if (!_ed25519_load_pk (self, options, TRUE, error))
|
||||
return FALSE;
|
||||
|
||||
/* Load untrusted keys from well-known directories and files
|
||||
* Ignore the failure from this function -- it is expected to have
|
||||
* empty list of revoked keys.
|
||||
* */
|
||||
if (!_ed25519_load_pk (self, options, FALSE, error))
|
||||
g_clear_error(error);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors:
|
||||
* - Denis Pynkin (d4s) <denis.pynkin@collabora.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ostree-sign.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define OSTREE_TYPE_SIGN_ED25519 (_ostree_sign_ed25519_get_type ())
|
||||
|
||||
GType _ostree_sign_ed25519_get_type (void);
|
||||
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
typedef struct _OstreeSignEd25519 OstreeSignEd25519;
|
||||
typedef struct { GObjectClass parent_class; } OstreeSignEd25519Class;
|
||||
|
||||
static inline OstreeSignEd25519 *OSTREE_SIGN_ED25519 (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, _ostree_sign_ed25519_get_type (), OstreeSignEd25519); }
|
||||
static inline gboolean OSTREE_IS_SIGN_ED25519 (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, _ostree_sign_ed25519_get_type ()); }
|
||||
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
/* Have to use glib-2.44 for this
|
||||
_OSTREE_PUBLIC
|
||||
G_DECLARE_FINAL_TYPE (OstreeSignEd25519,
|
||||
ostree_sign_ed25519,
|
||||
OSTREE,
|
||||
SIGN_ED25519,
|
||||
GObject)
|
||||
*/
|
||||
|
||||
gboolean ostree_sign_ed25519_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_ed25519_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error);
|
||||
|
||||
const gchar * ostree_sign_ed25519_get_name (OstreeSign *self);
|
||||
const gchar * ostree_sign_ed25519_metadata_key (OstreeSign *self);
|
||||
const gchar * ostree_sign_ed25519_metadata_format (OstreeSign *self);
|
||||
|
||||
gboolean ostree_sign_ed25519_clear_keys (OstreeSign *self,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_ed25519_set_sk (OstreeSign *self,
|
||||
GVariant *secret_key,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_ed25519_set_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_ed25519_add_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
|
||||
gboolean ostree_sign_ed25519_load_pk (OstreeSign *self,
|
||||
GVariant *options,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:ostree-sign
|
||||
* @title: Signature management
|
||||
* @short_description: Sign and verify commits
|
||||
*
|
||||
* An #OstreeSign interface allows to select and use any available engine
|
||||
* for signing or verifying the commit object or summary file.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include "libglnx.h"
|
||||
#include "otutil.h"
|
||||
|
||||
#include "ostree-autocleanups.h"
|
||||
#include "ostree-core.h"
|
||||
#include "ostree-sign.h"
|
||||
#include "ostree-sign-dummy.h"
|
||||
#ifdef HAVE_LIBSODIUM
|
||||
#include "ostree-sign-ed25519.h"
|
||||
#endif
|
||||
|
||||
#include "ostree-autocleanups.h"
|
||||
#include "ostree-repo-private.h"
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "OSTreeSign"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gchar *name;
|
||||
GType type;
|
||||
} _sign_type;
|
||||
|
||||
_sign_type sign_types[] =
|
||||
{
|
||||
#if defined(HAVE_LIBSODIUM)
|
||||
{OSTREE_SIGN_NAME_ED25519, 0},
|
||||
#endif
|
||||
{"dummy", 0}
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
#if defined(HAVE_LIBSODIUM)
|
||||
SIGN_ED25519,
|
||||
#endif
|
||||
SIGN_DUMMY
|
||||
};
|
||||
|
||||
G_DEFINE_INTERFACE (OstreeSign, ostree_sign, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
ostree_sign_default_init (OstreeSignInterface *iface)
|
||||
{
|
||||
g_debug ("OstreeSign initialization");
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_metadata_key:
|
||||
* @self: an #OstreeSign object
|
||||
*
|
||||
* Return the pointer to the name of the key used in (detached) metadata for
|
||||
* current signing engine.
|
||||
*
|
||||
* Returns: (transfer none): pointer to the metadata key name,
|
||||
* @NULL in case of error (unlikely).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
const gchar *
|
||||
ostree_sign_metadata_key (OstreeSign *self)
|
||||
{
|
||||
|
||||
g_return_val_if_fail (OSTREE_SIGN_GET_IFACE (self)->metadata_key != NULL, NULL);
|
||||
return OSTREE_SIGN_GET_IFACE (self)->metadata_key (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_metadata_format:
|
||||
* @self: an #OstreeSign object
|
||||
*
|
||||
* Return the pointer to the string with format used in (detached) metadata for
|
||||
* current signing engine.
|
||||
*
|
||||
* Returns: (transfer none): pointer to the metadata format,
|
||||
* @NULL in case of error (unlikely).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
const gchar *
|
||||
ostree_sign_metadata_format (OstreeSign *self)
|
||||
{
|
||||
|
||||
g_return_val_if_fail (OSTREE_SIGN_GET_IFACE (self)->metadata_format != NULL, NULL);
|
||||
return OSTREE_SIGN_GET_IFACE (self)->metadata_format (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_clear_keys:
|
||||
* @self: an #OstreeSign object
|
||||
* @error: a #GError
|
||||
*
|
||||
* Clear all previously preloaded secret and public keys.
|
||||
*
|
||||
* Returns: @TRUE in case if no errors, @FALSE in case of error
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_clear_keys (OstreeSign *self,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->clear_keys == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->clear_keys (self, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_set_sk:
|
||||
* @self: an #OstreeSign object
|
||||
* @secret_key: secret key to be added
|
||||
* @error: a #GError
|
||||
*
|
||||
* Set the secret key to be used for signing data, commits and summary.
|
||||
*
|
||||
* The @secret_key argument depends of the particular engine implementation.
|
||||
*
|
||||
* Returns: @TRUE in case if the key could be set successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_set_sk (OstreeSign *self,
|
||||
GVariant *secret_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->set_sk == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->set_sk (self, secret_key, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_set_pk:
|
||||
* @self: an #OstreeSign object
|
||||
* @public_key: single public key to be added
|
||||
* @error: a #GError
|
||||
*
|
||||
* Set the public key for verification. It is expected what all
|
||||
* previously pre-loaded public keys will be dropped.
|
||||
*
|
||||
* The @public_key argument depends of the particular engine implementation.
|
||||
*
|
||||
* Returns: @TRUE in case if the key could be set successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_set_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->set_pk == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->set_pk (self, public_key, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_add_pk:
|
||||
* @self: an #OstreeSign object
|
||||
* @public_key: single public key to be added
|
||||
* @error: a #GError
|
||||
*
|
||||
* Add the public key for verification. Could be called multiple times for
|
||||
* adding all needed keys to be used for verification.
|
||||
*
|
||||
* The @public_key argument depends of the particular engine implementation.
|
||||
*
|
||||
* Returns: @TRUE in case if the key could be added successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_add_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->add_pk == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->add_pk (self, public_key, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_load_pk:
|
||||
* @self: an #OstreeSign object
|
||||
* @options: any options
|
||||
* @error: a #GError
|
||||
*
|
||||
* Load public keys for verification from anywhere.
|
||||
* It is expected that all keys would be added to already pre-loaded keys.
|
||||
*
|
||||
* The @options argument depends of the particular engine implementation.
|
||||
*
|
||||
* For example, @ed25515 engine could use following string-formatted options:
|
||||
* - @filename -- single file to use to load keys from
|
||||
* - @basedir -- directory containing subdirectories
|
||||
* 'trusted.ed25519.d' and 'revoked.ed25519.d' with appropriate
|
||||
* public keys. Used for testing and re-definition of system-wide
|
||||
* directories if defaults are not suitable for any reason.
|
||||
*
|
||||
* Returns: @TRUE in case if at least one key could be load successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
/*
|
||||
* No need to have similar function for secret keys load -- it is expected
|
||||
* what the signing software will load the secret key in it's own way.
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_load_pk (OstreeSign *self,
|
||||
GVariant *options,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->load_pk == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->load_pk (self, options, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_data:
|
||||
* @self: an #OstreeSign object
|
||||
* @data: the raw data to be signed with pre-loaded secret key
|
||||
* @signature: in case of success will contain signature
|
||||
* @cancellable: A #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Sign the given @data with pre-loaded secret key.
|
||||
*
|
||||
* Depending of the signing engine used you will need to load
|
||||
* the secret key with #ostree_sign_set_sk.
|
||||
*
|
||||
* Returns: @TRUE if @data has been signed successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->data == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->data (self, data, signature, cancellable, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_data_verify:
|
||||
* @self: an #OstreeSign object
|
||||
* @data: the raw data to check
|
||||
* @signatures: the signatures to be checked
|
||||
* @error: a #GError
|
||||
*
|
||||
* Verify given data against signatures with pre-loaded public keys.
|
||||
*
|
||||
* Depending of the signing engine used you will need to load
|
||||
* the public key(s) with #ostree_sign_set_pk, #ostree_sign_add_pk
|
||||
* or #ostree_sign_load_pk.
|
||||
*
|
||||
* Returns: @TRUE if @data has been signed at least with any single valid key,
|
||||
* @FALSE in case of error or no valid keys are available (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
if (OSTREE_SIGN_GET_IFACE (self)->data_verify == NULL)
|
||||
return glnx_throw (error, "not implemented");
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->data_verify(self, data, signatures, out_success_message, error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adopted version of _ostree_detached_metadata_append_gpg_sig ()
|
||||
*/
|
||||
static GVariant *
|
||||
_sign_detached_metadata_append (OstreeSign *self,
|
||||
GVariant *existing_metadata,
|
||||
GBytes *signature_bytes)
|
||||
{
|
||||
g_return_val_if_fail (signature_bytes != NULL, FALSE);
|
||||
|
||||
GVariantDict metadata_dict;
|
||||
g_autoptr(GVariant) signature_data = NULL;
|
||||
g_autoptr(GVariantBuilder) signature_builder = NULL;
|
||||
|
||||
g_variant_dict_init (&metadata_dict, existing_metadata);
|
||||
|
||||
const gchar *signature_key = ostree_sign_metadata_key(self);
|
||||
GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(self);
|
||||
|
||||
signature_data = g_variant_dict_lookup_value (&metadata_dict,
|
||||
signature_key,
|
||||
(GVariantType*)signature_format);
|
||||
|
||||
/* signature_data may be NULL */
|
||||
signature_builder = ot_util_variant_builder_from_variant (signature_data, signature_format);
|
||||
|
||||
g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes));
|
||||
|
||||
g_variant_dict_insert_value (&metadata_dict,
|
||||
signature_key,
|
||||
g_variant_builder_end (signature_builder));
|
||||
|
||||
return g_variant_dict_end (&metadata_dict);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_commit_verify:
|
||||
* @self: an #OstreeSign object
|
||||
* @repo: an #OsreeRepo object
|
||||
* @commit_checksum: SHA256 of given commit to verify
|
||||
* @cancellable: A #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Verify if commit is signed with known key.
|
||||
*
|
||||
* Depending of the signing engine used you will need to load
|
||||
* the public key(s) for verification with #ostree_sign_set_pk,
|
||||
* #ostree_sign_add_pk and/or #ostree_sign_load_pk.
|
||||
*
|
||||
* Returns: @TRUE if commit has been verified successfully,
|
||||
* @FALSE in case of error or no valid keys are available (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_commit_verify (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
const gchar *commit_checksum,
|
||||
char **out_success_message,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
|
||||
g_autoptr(GVariant) commit_variant = NULL;
|
||||
/* Load the commit */
|
||||
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT,
|
||||
commit_checksum, &commit_variant,
|
||||
error))
|
||||
return glnx_prefix_error (error, "Failed to read commit");
|
||||
|
||||
/* Load the metadata */
|
||||
g_autoptr(GVariant) metadata = NULL;
|
||||
if (!ostree_repo_read_commit_detached_metadata (repo,
|
||||
commit_checksum,
|
||||
&metadata,
|
||||
cancellable,
|
||||
error))
|
||||
return glnx_prefix_error (error, "Failed to read detached metadata");
|
||||
|
||||
g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes (commit_variant);
|
||||
|
||||
g_autoptr(GVariant) signatures = NULL;
|
||||
|
||||
const gchar *signature_key = ostree_sign_metadata_key(self);
|
||||
GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(self);
|
||||
|
||||
if (metadata)
|
||||
signatures = g_variant_lookup_value (metadata,
|
||||
signature_key,
|
||||
signature_format);
|
||||
|
||||
|
||||
return ostree_sign_data_verify (self,
|
||||
signed_data,
|
||||
signatures,
|
||||
out_success_message,
|
||||
error);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_get_name:
|
||||
* @self: an #OstreeSign object
|
||||
*
|
||||
* Return the pointer to the name of currently used/selected signing engine.
|
||||
*
|
||||
* Returns: (transfer none): pointer to the name
|
||||
* @NULL in case of error (unlikely).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
const gchar *
|
||||
ostree_sign_get_name (OstreeSign *self)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), NULL);
|
||||
g_return_val_if_fail (OSTREE_SIGN_GET_IFACE (self)->get_name != NULL, NULL);
|
||||
|
||||
return OSTREE_SIGN_GET_IFACE (self)->get_name (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_commit:
|
||||
* @self: an #OstreeSign object
|
||||
* @repo: an #OsreeRepo object
|
||||
* @commit_checksum: SHA256 of given commit to sign
|
||||
* @cancellable: A #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Add a signature to a commit.
|
||||
*
|
||||
* Depending of the signing engine used you will need to load
|
||||
* the secret key with #ostree_sign_set_sk.
|
||||
*
|
||||
* Returns: @TRUE if commit has been signed successfully,
|
||||
* @FALSE in case of error (@error will contain the reason).
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_commit (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
const gchar *commit_checksum,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
|
||||
g_autoptr(GBytes) commit_data = NULL;
|
||||
g_autoptr(GBytes) signature = NULL;
|
||||
g_autoptr(GVariant) commit_variant = NULL;
|
||||
g_autoptr(GVariant) old_metadata = NULL;
|
||||
g_autoptr(GVariant) new_metadata = NULL;
|
||||
|
||||
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT,
|
||||
commit_checksum, &commit_variant, error))
|
||||
return glnx_prefix_error (error, "Failed to read commit");
|
||||
|
||||
if (!ostree_repo_read_commit_detached_metadata (repo,
|
||||
commit_checksum,
|
||||
&old_metadata,
|
||||
cancellable,
|
||||
error))
|
||||
return glnx_prefix_error (error, "Failed to read detached metadata");
|
||||
|
||||
commit_data = g_variant_get_data_as_bytes (commit_variant);
|
||||
|
||||
if (!ostree_sign_data (self, commit_data, &signature,
|
||||
cancellable, error))
|
||||
return glnx_prefix_error (error, "Not able to sign the cobject");
|
||||
|
||||
new_metadata =
|
||||
_sign_detached_metadata_append (self, old_metadata, signature);
|
||||
|
||||
if (!ostree_repo_write_commit_detached_metadata (repo,
|
||||
commit_checksum,
|
||||
new_metadata,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_get_all:
|
||||
*
|
||||
* Return an array with newly allocated instances of all available
|
||||
* signing engines; they will not be initialized.
|
||||
*
|
||||
* Returns: (transfer full) (element-type OstreeSign): an array of signing engines
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
GPtrArray *
|
||||
ostree_sign_get_all (void)
|
||||
{
|
||||
g_autoptr(GPtrArray) engines = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
for (guint i = 0; i < G_N_ELEMENTS(sign_types); i++)
|
||||
{
|
||||
OstreeSign *engine = ostree_sign_get_by_name (sign_types[i].name, NULL);
|
||||
g_assert (engine);
|
||||
g_ptr_array_add (engines, engine);
|
||||
}
|
||||
|
||||
return g_steal_pointer (&engines);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_get_by_name:
|
||||
* @name: the name of desired signature engine
|
||||
* @error: return location for a #GError
|
||||
*
|
||||
* Create a new instance of a signing engine.
|
||||
*
|
||||
* Returns: (transfer full): New signing engine, or %NULL if the engine is not known
|
||||
*
|
||||
* Since: 2020.2
|
||||
*/
|
||||
OstreeSign *
|
||||
ostree_sign_get_by_name (const gchar *name, GError **error)
|
||||
{
|
||||
|
||||
OstreeSign *sign = NULL;
|
||||
|
||||
/* Get types if not initialized yet */
|
||||
#if defined(HAVE_LIBSODIUM)
|
||||
if (sign_types[SIGN_ED25519].type == 0)
|
||||
sign_types[SIGN_ED25519].type = OSTREE_TYPE_SIGN_ED25519;
|
||||
#endif
|
||||
if (sign_types[SIGN_DUMMY].type == 0)
|
||||
sign_types[SIGN_DUMMY].type = OSTREE_TYPE_SIGN_DUMMY;
|
||||
|
||||
for (gint i=0; i < G_N_ELEMENTS(sign_types); i++)
|
||||
{
|
||||
if (g_strcmp0 (name, sign_types[i].name) == 0)
|
||||
{
|
||||
g_debug ("Using '%s' signing engine", sign_types[i].name);
|
||||
sign = g_object_new (sign_types[i].type, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sign == NULL)
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Requested signature type is not implemented");
|
||||
|
||||
return sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sign_summary:
|
||||
* @self: Self
|
||||
* @repo: ostree repository
|
||||
* @keys: keys -- GVariant containing keys as GVarints specific to signature type.
|
||||
* @cancellable: A #GCancellable
|
||||
* @error: a #GError
|
||||
*
|
||||
* Add a signature to a summary file.
|
||||
* Based on ostree_repo_add_gpg_signature_summary implementation.
|
||||
*
|
||||
* Returns: @TRUE if summary file has been signed with all provided keys
|
||||
*/
|
||||
gboolean
|
||||
ostree_sign_summary (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
GVariant *keys,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SIGN (self), FALSE);
|
||||
g_return_val_if_fail (OSTREE_IS_REPO (repo), FALSE);
|
||||
|
||||
g_autoptr(GVariant) normalized = NULL;
|
||||
g_autoptr(GBytes) summary_data = NULL;
|
||||
g_autoptr(GVariant) metadata = NULL;
|
||||
|
||||
glnx_autofd int fd = -1;
|
||||
if (!glnx_openat_rdonly (repo->repo_dir_fd, "summary", TRUE, &fd, error))
|
||||
return FALSE;
|
||||
summary_data = ot_fd_readall_or_mmap (fd, 0, error);
|
||||
if (!summary_data)
|
||||
return FALSE;
|
||||
|
||||
/* Note that fd is reused below */
|
||||
glnx_close_fd (&fd);
|
||||
|
||||
if (!ot_openat_ignore_enoent (repo->repo_dir_fd, "summary.sig", &fd, error))
|
||||
return FALSE;
|
||||
|
||||
if (fd >= 0)
|
||||
{
|
||||
if (!ot_variant_read_fd (fd, 0, OSTREE_SUMMARY_SIG_GVARIANT_FORMAT,
|
||||
FALSE, &metadata, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (g_variant_n_children(keys) == 0)
|
||||
return glnx_throw (error, "No keys passed for signing summary");
|
||||
|
||||
GVariantIter *iter;
|
||||
GVariant *key;
|
||||
|
||||
g_variant_get (keys, "av", &iter);
|
||||
while (g_variant_iter_loop (iter, "v", &key))
|
||||
{
|
||||
g_autoptr (GBytes) signature = NULL;
|
||||
|
||||
if (!ostree_sign_set_sk (self, key, error))
|
||||
return FALSE;
|
||||
|
||||
if (!ostree_sign_data (self,
|
||||
summary_data,
|
||||
&signature,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(GVariant) old_metadata = g_steal_pointer (&metadata);
|
||||
metadata =
|
||||
_sign_detached_metadata_append (self, old_metadata, signature);
|
||||
}
|
||||
g_variant_iter_free (iter);
|
||||
|
||||
normalized = g_variant_get_normal_form (metadata);
|
||||
if (!_ostree_repo_file_replace_contents (repo,
|
||||
repo->repo_dir_fd,
|
||||
"summary.sig",
|
||||
g_variant_get_data (normalized),
|
||||
g_variant_get_size (normalized),
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Collabora Ltd.
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors:
|
||||
* - Denis Pynkin (d4s) <denis.pynkin@collabora.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "ostree-ref.h"
|
||||
#include "ostree-remote.h"
|
||||
#include "ostree-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define OSTREE_TYPE_SIGN (ostree_sign_get_type ())
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
GType ostree_sign_get_type (void);
|
||||
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
typedef struct _OstreeSign OstreeSign;
|
||||
typedef struct _OstreeSignInterface OstreeSignInterface;
|
||||
|
||||
static inline OstreeSign *OSTREE_SIGN (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_sign_get_type (), OstreeSign); }
|
||||
static inline gboolean OSTREE_IS_SIGN (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_sign_get_type ()); }
|
||||
static inline OstreeSignInterface *OSTREE_SIGN_GET_IFACE (gpointer ptr) { return G_TYPE_INSTANCE_GET_INTERFACE (ptr, ostree_sign_get_type (), OstreeSignInterface); }
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
/**
|
||||
* OSTREE_SIGN_NAME_ED25519:
|
||||
* The name of the default ed25519 signing type.
|
||||
*/
|
||||
#define OSTREE_SIGN_NAME_ED25519 "ed25519"
|
||||
|
||||
/* Have to use glib-2.44 for this
|
||||
_OSTREE_PUBLIC
|
||||
G_DECLARE_INTERFACE (OstreeSign, ostree_sign, OSTREE, SIGN, GObject)
|
||||
*/
|
||||
|
||||
struct _OstreeSignInterface
|
||||
{
|
||||
GTypeInterface g_iface;
|
||||
const gchar *(* get_name) (OstreeSign *self);
|
||||
gboolean (* data) (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
gboolean (* data_verify) (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error);
|
||||
const gchar *(* metadata_key) (OstreeSign *self);
|
||||
const gchar *(* metadata_format) (OstreeSign *self);
|
||||
gboolean (* clear_keys) (OstreeSign *self,
|
||||
GError **error);
|
||||
gboolean (* set_sk) (OstreeSign *self,
|
||||
GVariant *secret_key,
|
||||
GError **error);
|
||||
gboolean (* set_pk) (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
gboolean (* add_pk) (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
gboolean (* load_pk) (OstreeSign *self,
|
||||
GVariant *options,
|
||||
GError **error);
|
||||
};
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
const gchar * ostree_sign_get_name (OstreeSign *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_data (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GBytes **signature,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_data_verify (OstreeSign *self,
|
||||
GBytes *data,
|
||||
GVariant *signatures,
|
||||
char **out_success_message,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
const gchar * ostree_sign_metadata_key (OstreeSign *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
const gchar * ostree_sign_metadata_format (OstreeSign *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_commit (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
const gchar *commit_checksum,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_commit_verify (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
const gchar *commit_checksum,
|
||||
char **out_success_message,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_clear_keys (OstreeSign *self,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_set_sk (OstreeSign *self,
|
||||
GVariant *secret_key,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_set_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_add_pk (OstreeSign *self,
|
||||
GVariant *public_key,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_load_pk (OstreeSign *self,
|
||||
GVariant *options,
|
||||
GError **error);
|
||||
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
GPtrArray * ostree_sign_get_all(void);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
OstreeSign * ostree_sign_get_by_name (const gchar *name, GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sign_summary (OstreeSign *self,
|
||||
OstreeRepo *repo,
|
||||
GVariant *keys,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
G_END_DECLS
|
||||
|
||||
@@ -217,6 +217,79 @@ dirfd_copy_attributes_and_xattrs (int src_parent_dfd,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gint
|
||||
str_sort_cb (gconstpointer name_ptr_a, gconstpointer name_ptr_b)
|
||||
{
|
||||
const gchar *name_a = *((const gchar **) name_ptr_a);
|
||||
const gchar *name_b = *((const gchar **) name_ptr_b);
|
||||
|
||||
return g_strcmp0 (name_a, name_b);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
checksum_dir_recurse (int dfd,
|
||||
const char *path,
|
||||
OtChecksum *checksum,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_auto(GLnxDirFdIterator) dfditer = { 0, };
|
||||
g_autoptr (GPtrArray) d_entries = g_ptr_array_new_with_free_func (g_free);
|
||||
|
||||
if (!glnx_dirfd_iterator_init_at (dfd, path, TRUE, &dfditer, error))
|
||||
return FALSE;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
struct dirent *dent;
|
||||
|
||||
if (!glnx_dirfd_iterator_next_dent (&dfditer, &dent, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (dent == NULL)
|
||||
break;
|
||||
|
||||
g_ptr_array_add (d_entries, g_strdup (dent->d_name));
|
||||
}
|
||||
|
||||
/* File systems do not guarantee dir entry order, make sure this is
|
||||
* reproducable
|
||||
*/
|
||||
g_ptr_array_sort(d_entries, str_sort_cb);
|
||||
|
||||
for (gint i=0; i < d_entries->len; i++)
|
||||
{
|
||||
const gchar *d_name = (gchar *)g_ptr_array_index (d_entries, i);
|
||||
struct stat stbuf;
|
||||
|
||||
if (!glnx_fstatat (dfditer.fd, d_name, &stbuf,
|
||||
AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
|
||||
if (S_ISDIR (stbuf.st_mode))
|
||||
{
|
||||
if (!checksum_dir_recurse(dfditer.fd, d_name, checksum, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
int fd;
|
||||
|
||||
if (!ot_openat_ignore_enoent (dfditer.fd, d_name, &fd, error))
|
||||
return FALSE;
|
||||
if (fd != -1)
|
||||
{
|
||||
g_autoptr(GInputStream) in = g_unix_input_stream_new (fd, FALSE);
|
||||
if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
copy_dir_recurse (int src_parent_dfd,
|
||||
int dest_parent_dfd,
|
||||
@@ -1065,6 +1138,9 @@ get_kernel_from_tree_usrlib_modules (int deployment_dfd,
|
||||
g_clear_object (&in);
|
||||
glnx_close_fd (&fd);
|
||||
|
||||
/* Check for /usr/lib/modules/$kver/devicetree first, if it does not
|
||||
* exist check for /usr/lib/modules/$kver/dtb/ directory.
|
||||
*/
|
||||
if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "devicetree", &fd, error))
|
||||
return FALSE;
|
||||
if (fd != -1)
|
||||
@@ -1075,6 +1151,23 @@ get_kernel_from_tree_usrlib_modules (int deployment_dfd,
|
||||
if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct stat stbuf;
|
||||
/* Check for dtb directory */
|
||||
if (!glnx_fstatat_allow_noent (ret_layout->boot_dfd, "dtb", &stbuf, 0, error))
|
||||
return FALSE;
|
||||
|
||||
if (errno == 0 && S_ISDIR (stbuf.st_mode))
|
||||
{
|
||||
/* devicetree_namever set to NULL indicates a complete directory */
|
||||
ret_layout->devicetree_srcpath = g_strdup ("dtb");
|
||||
ret_layout->devicetree_namever = NULL;
|
||||
|
||||
if (!checksum_dir_recurse(ret_layout->boot_dfd, "dtb", &checksum, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_clear_object (&in);
|
||||
glnx_close_fd (&fd);
|
||||
@@ -1730,15 +1823,24 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
|
||||
if (kernel_layout->devicetree_srcpath)
|
||||
{
|
||||
g_assert (kernel_layout->devicetree_namever);
|
||||
if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->devicetree_namever, &stbuf, 0, error))
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
/* If devicetree_namever is set a single device tree is deployed */
|
||||
if (kernel_layout->devicetree_namever)
|
||||
{
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
|
||||
bootcsum_dfd, kernel_layout->devicetree_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
if (!glnx_fstatat_allow_noent (bootcsum_dfd, kernel_layout->devicetree_namever, &stbuf, 0, error))
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
|
||||
bootcsum_dfd, kernel_layout->devicetree_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!copy_dir_recurse(kernel_layout->boot_dfd, bootcsum_dfd, kernel_layout->devicetree_srcpath,
|
||||
sysroot->debug_flags, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
@@ -1850,6 +1952,15 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL);
|
||||
ostree_bootconfig_parser_set (bootconfig, "devicetree", boot_relpath);
|
||||
}
|
||||
else if (kernel_layout->devicetree_srcpath)
|
||||
{
|
||||
/* If devicetree_srcpath is set but devicetree_namever is NULL, then we
|
||||
* want to point to a whole directory of device trees.
|
||||
* See: https://github.com/ostreedev/ostree/issues/1900
|
||||
*/
|
||||
g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_srcpath, NULL);
|
||||
ostree_bootconfig_parser_set (bootconfig, "fdtdir", boot_relpath);
|
||||
}
|
||||
|
||||
/* Note this is parsed in ostree-impl-system-generator.c */
|
||||
g_autofree char *ostree_kernel_arg = g_strdup_printf ("ostree=/ostree/boot.%d/%s/%s/%d",
|
||||
|
||||
@@ -530,8 +530,8 @@ ostree_sysroot_upgrader_pull_one_dir (OstreeSysrootUpgrader *self,
|
||||
g_variant_new_variant (g_variant_new_int32 (flags)));
|
||||
/* Add the timestamp check, unless disabled */
|
||||
if ((upgrader_flags & OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER) == 0)
|
||||
g_variant_builder_add (optbuilder, "{s@v}", "timestamp-check",
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
g_variant_builder_add (optbuilder, "{s@v}", "timestamp-check-from-rev",
|
||||
g_variant_new_variant (g_variant_new_string (from_revision)));
|
||||
|
||||
g_variant_builder_add (optbuilder, "{s@v}", "refs",
|
||||
g_variant_new_variant (g_variant_new_strv ((const char *const*) refs_to_fetch, -1)));
|
||||
|
||||
@@ -988,30 +988,12 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_load_if_changed:
|
||||
* @self: #OstreeSysroot
|
||||
* @out_changed: (out caller-allocates):
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Since: 2016.4
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
gboolean *out_changed,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
static gboolean
|
||||
sysroot_load_from_bootloader_configs (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!ostree_sysroot_initialize (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Here we also lazily initialize the repository. We didn't do this
|
||||
* previous to v2017.6, but we do now to support the error-free
|
||||
* ostree_sysroot_repo() API.
|
||||
*/
|
||||
if (!ensure_repo (self, error))
|
||||
return FALSE;
|
||||
struct stat stbuf;
|
||||
|
||||
int bootversion = 0;
|
||||
if (!read_current_bootversion (self, &bootversion, cancellable, error))
|
||||
@@ -1022,27 +1004,6 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
struct stat stbuf;
|
||||
if (!glnx_fstatat (self->sysroot_fd, "ostree/deploy", &stbuf, 0, error))
|
||||
return FALSE;
|
||||
|
||||
if (out_changed)
|
||||
{
|
||||
if (self->loaded_ts.tv_sec == stbuf.st_mtim.tv_sec &&
|
||||
self->loaded_ts.tv_nsec == stbuf.st_mtim.tv_nsec)
|
||||
{
|
||||
*out_changed = FALSE;
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->deployments, g_ptr_array_unref);
|
||||
g_clear_object (&self->booted_deployment);
|
||||
g_clear_object (&self->staged_deployment);
|
||||
self->bootversion = -1;
|
||||
self->subbootversion = -1;
|
||||
|
||||
g_autoptr(GPtrArray) boot_loader_configs = NULL;
|
||||
if (!_ostree_sysroot_read_boot_loader_configs (self, bootversion, &boot_loader_configs,
|
||||
cancellable, error))
|
||||
@@ -1120,8 +1081,60 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
|
||||
self->bootversion = bootversion;
|
||||
self->subbootversion = subbootversion;
|
||||
self->deployments = deployments;
|
||||
deployments = NULL; /* Transfer ownership */
|
||||
self->deployments = g_steal_pointer (&deployments);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_load_if_changed:
|
||||
* @self: #OstreeSysroot
|
||||
* @out_changed: (out caller-allocates):
|
||||
* @cancellable: Cancellable
|
||||
* @error: Error
|
||||
*
|
||||
* Since: 2016.4
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
gboolean *out_changed,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("loading sysroot", error);
|
||||
|
||||
if (!ostree_sysroot_initialize (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Here we also lazily initialize the repository. We didn't do this
|
||||
* previous to v2017.6, but we do now to support the error-free
|
||||
* ostree_sysroot_repo() API.
|
||||
*/
|
||||
if (!ensure_repo (self, error))
|
||||
return FALSE;
|
||||
|
||||
struct stat stbuf;
|
||||
if (!glnx_fstatat (self->sysroot_fd, "ostree/deploy", &stbuf, 0, error))
|
||||
return FALSE;
|
||||
|
||||
if (self->loaded_ts.tv_sec == stbuf.st_mtim.tv_sec &&
|
||||
self->loaded_ts.tv_nsec == stbuf.st_mtim.tv_nsec)
|
||||
{
|
||||
if (out_changed)
|
||||
*out_changed = FALSE;
|
||||
/* Note early return */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->deployments, g_ptr_array_unref);
|
||||
g_clear_object (&self->booted_deployment);
|
||||
g_clear_object (&self->staged_deployment);
|
||||
self->bootversion = -1;
|
||||
self->subbootversion = -1;
|
||||
|
||||
if (!sysroot_load_from_bootloader_configs (self, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
self->loaded_ts = stbuf.st_mtim;
|
||||
|
||||
if (out_changed)
|
||||
|
||||
@@ -101,9 +101,6 @@ GFile * ostree_sysroot_get_deployment_origin_path (GFile *deployment_path);
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_lock (OstreeSysroot *self, GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_lock_with_mount_namespace (OstreeSysroot *self, GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_try_lock (OstreeSysroot *self,
|
||||
gboolean *out_acquired,
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_RELEASE_VERSION (3)
|
||||
#define OSTREE_RELEASE_VERSION (4)
|
||||
|
||||
/**
|
||||
* OSTREE_VERSION
|
||||
@@ -52,7 +52,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_VERSION (2020.3)
|
||||
#define OSTREE_VERSION (2020.4)
|
||||
|
||||
/**
|
||||
* OSTREE_VERSION_S:
|
||||
@@ -62,7 +62,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_VERSION_S "2020.3"
|
||||
#define OSTREE_VERSION_S "2020.4"
|
||||
|
||||
#define OSTREE_ENCODE_VERSION(year,release) \
|
||||
((year) << 16 | (release))
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
#include <ostree-repo-finder-mount.h>
|
||||
#include <ostree-repo-finder-override.h>
|
||||
#include <ostree-kernel-args.h>
|
||||
#include <ostree-sign.h>
|
||||
#include <ostree-autocleanups.h>
|
||||
#include <ostree-version.h>
|
||||
@@ -109,6 +109,9 @@ static OstreeCommand commands[] = {
|
||||
{ "rev-parse", OSTREE_BUILTIN_FLAG_NONE,
|
||||
ostree_builtin_rev_parse,
|
||||
"Output the target of a rev" },
|
||||
{ "sign", OSTREE_BUILTIN_FLAG_NONE,
|
||||
ostree_builtin_sign,
|
||||
"Sign a commit" },
|
||||
{ "show", OSTREE_BUILTIN_FLAG_NONE,
|
||||
ostree_builtin_show,
|
||||
"Output a metadata object" },
|
||||
|
||||
@@ -37,6 +37,7 @@ static gboolean opt_retain_pending;
|
||||
static gboolean opt_retain_rollback;
|
||||
static gboolean opt_not_as_default;
|
||||
static gboolean opt_no_prune;
|
||||
static gboolean opt_no_merge;
|
||||
static char **opt_kernel_argv;
|
||||
static char **opt_kernel_argv_append;
|
||||
static gboolean opt_kernel_proc_cmdline;
|
||||
@@ -48,6 +49,7 @@ static GOptionEntry options[] = {
|
||||
{ "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" },
|
||||
{ "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", "FILENAME" },
|
||||
{ "no-prune", 0, 0, G_OPTION_ARG_NONE, &opt_no_prune, "Don't prune the repo when done", NULL},
|
||||
{ "no-merge", 0, 0, G_OPTION_ARG_NONE, &opt_no_merge, "Do not apply configuration (/etc and kernel arguments) from booted deployment", NULL},
|
||||
{ "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployments", NULL },
|
||||
{ "stage", 0, 0, G_OPTION_ARG_NONE, &opt_stage, "Complete deployment at OS shutdown", NULL },
|
||||
{ "retain-pending", 0, 0, G_OPTION_ARG_NONE, &opt_retain_pending, "Do not delete pending deployments", NULL },
|
||||
@@ -113,7 +115,7 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(OstreeDeployment) merge_deployment =
|
||||
ostree_sysroot_get_merge_deployment (sysroot, opt_osname);
|
||||
opt_no_merge ? NULL : ostree_sysroot_get_merge_deployment (sysroot, opt_osname);
|
||||
|
||||
/* Here we perform cleanup of any leftover data from previous
|
||||
* partial failures. This avoids having to call
|
||||
|
||||
@@ -52,39 +52,28 @@ ot_admin_builtin_pin (int argc, char **argv, OstreeCommandInvocation *invocation
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
unsigned int nsuccess = 0;
|
||||
for (unsigned int i = 1; i < argc; i++)
|
||||
{
|
||||
const char *deploy_index_str = argv[i];
|
||||
const int deploy_index = atoi (deploy_index_str);
|
||||
|
||||
g_autoptr(GError) e = NULL;
|
||||
g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, &e);
|
||||
g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, error);
|
||||
if (!target_deployment)
|
||||
{
|
||||
g_print ("Invalid deployment %s: %s\n", deploy_index_str, e->message);
|
||||
continue;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
gboolean current_pin = ostree_deployment_is_pinned (target_deployment);
|
||||
const gboolean desired_pin = !opt_unpin;
|
||||
if (current_pin == desired_pin)
|
||||
{
|
||||
g_print ("Deployment %s is already %s\n", deploy_index_str, current_pin ? "pinned" : "unpinned");
|
||||
nsuccess++;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_autoptr(GError) e = NULL;
|
||||
if (ostree_sysroot_deployment_set_pinned (sysroot, target_deployment, desired_pin, &e))
|
||||
{
|
||||
g_print ("Deployment %s is now %s\n", deploy_index_str, desired_pin ? "pinned" : "unpinned");
|
||||
nsuccess++;
|
||||
}
|
||||
else
|
||||
g_print ("Failed to %s deployment %s: %s\n", desired_pin ? "pin" : "unpin", deploy_index_str, e->message);
|
||||
if (!ostree_sysroot_deployment_set_pinned (sysroot, target_deployment, desired_pin, error))
|
||||
return FALSE;
|
||||
g_print ("Deployment %s is now %s\n", deploy_index_str, desired_pin ? "pinned" : "unpinned");
|
||||
}
|
||||
}
|
||||
|
||||
return nsuccess > 0;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ static GOptionEntry options[] = {
|
||||
{ "force-copy-zerosized", 'z', 0, G_OPTION_ARG_NONE, &opt_force_copy_zerosized, "Do not hardlink zero-sized files", NULL },
|
||||
{ "force-copy", 'C', 0, G_OPTION_ARG_NONE, &opt_force_copy, "Never hardlink (but may reflink if available)", NULL },
|
||||
{ "bareuseronly-dirs", 'M', 0, G_OPTION_ARG_NONE, &opt_bareuseronly_dirs, "Suppress mode bits outside of 0775 for directories (suid, world writable, etc.)", NULL },
|
||||
{ "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "PATH" },
|
||||
{ "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "FILE" },
|
||||
{ "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /); implies --force-copy", "PATH" },
|
||||
{ "selinux-prefix", 0, 0, G_OPTION_ARG_STRING, &opt_selinux_prefix, "When setting SELinux labels, prefix all paths by PREFIX", "PREFIX" },
|
||||
{ NULL }
|
||||
|
||||
+226
-134
@@ -31,6 +31,7 @@
|
||||
#include "parse-datetime.h"
|
||||
#include "ostree-repo-private.h"
|
||||
#include "ostree-libarchive-private.h"
|
||||
#include "ostree-sign.h"
|
||||
|
||||
static char *opt_subject;
|
||||
static char *opt_body;
|
||||
@@ -53,17 +54,22 @@ static gboolean opt_tar_autocreate_parents;
|
||||
static char *opt_tar_pathname_filter;
|
||||
static gboolean opt_no_xattrs;
|
||||
static char *opt_selinux_policy;
|
||||
static gboolean opt_selinux_policy_from_base;
|
||||
static gboolean opt_canonical_permissions;
|
||||
static gboolean opt_ro_executables;
|
||||
static gboolean opt_consume;
|
||||
static gboolean opt_devino_canonical;
|
||||
static char *opt_base;
|
||||
static char **opt_trees;
|
||||
static gint opt_owner_uid = -1;
|
||||
static gint opt_owner_gid = -1;
|
||||
static gboolean opt_table_output;
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
static char **opt_key_ids;
|
||||
static char **opt_gpg_key_ids;
|
||||
static char *opt_gpg_homedir;
|
||||
#endif
|
||||
static char **opt_key_ids;
|
||||
static char *opt_sign_name;
|
||||
static gboolean opt_generate_sizes;
|
||||
static gboolean opt_disable_fsync;
|
||||
static char *opt_timestamp;
|
||||
@@ -97,6 +103,7 @@ static GOptionEntry options[] = {
|
||||
{ "orphan", 0, 0, G_OPTION_ARG_NONE, &opt_orphan, "Create a commit without writing a ref", NULL },
|
||||
{ "no-bindings", 0, 0, G_OPTION_ARG_NONE, &opt_no_bindings, "Do not write any ref bindings", NULL },
|
||||
{ "bind-ref", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_bind_refs, "Add a ref to ref binding commit metadata", "BRANCH" },
|
||||
{ "base", 0, 0, G_OPTION_ARG_STRING, &opt_base, "Start from the given commit as a base (no modifiers apply)", "REF" },
|
||||
{ "tree", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_trees, "Overlay the given argument as a tree", "dir=PATH or tar=TARFILE or ref=COMMIT" },
|
||||
{ "add-metadata-string", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_strings, "Add a key/value pair to metadata", "KEY=VALUE" },
|
||||
{ "add-metadata", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_variants, "Add a key/value pair to metadata, where the KEY is a string, an VALUE is g_variant_parse() formatted", "KEY=VALUE" },
|
||||
@@ -105,8 +112,10 @@ static GOptionEntry options[] = {
|
||||
{ "owner-uid", 0, 0, G_OPTION_ARG_INT, &opt_owner_uid, "Set file ownership user id", "UID" },
|
||||
{ "owner-gid", 0, 0, G_OPTION_ARG_INT, &opt_owner_gid, "Set file ownership group id", "GID" },
|
||||
{ "canonical-permissions", 0, 0, G_OPTION_ARG_NONE, &opt_canonical_permissions, "Canonicalize permissions in the same way bare-user does for hardlinked files", NULL },
|
||||
{ "mode-ro-executables", 0, 0, G_OPTION_ARG_NONE, &opt_ro_executables, "Ensure executable files are not writable", NULL },
|
||||
{ "no-xattrs", 0, 0, G_OPTION_ARG_NONE, &opt_no_xattrs, "Do not import extended attributes", NULL },
|
||||
{ "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /)", "PATH" },
|
||||
{ "selinux-policy-from-base", 'P', 0, G_OPTION_ARG_NONE, &opt_selinux_policy_from_base, "Set SELinux labels based on first --tree argument", NULL },
|
||||
{ "link-checkout-speedup", 0, 0, G_OPTION_ARG_NONE, &opt_link_checkout_speedup, "Optimize for commits of trees composed of hardlinks into the repository", NULL },
|
||||
{ "devino-canonical", 'I', 0, G_OPTION_ARG_NONE, &opt_devino_canonical, "Assume hardlinked objects are unmodified. Implies --link-checkout-speedup", NULL },
|
||||
{ "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &opt_tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL },
|
||||
@@ -117,9 +126,11 @@ static GOptionEntry options[] = {
|
||||
{ "consume", 0, 0, G_OPTION_ARG_NONE, &opt_consume, "Consume (delete) content after commit (for local directories)", NULL },
|
||||
{ "table-output", 0, 0, G_OPTION_ARG_NONE, &opt_table_output, "Output more information in a KEY: VALUE format", NULL },
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the commit with", "KEY-ID"},
|
||||
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_gpg_key_ids, "GPG Key ID to sign the commit with", "KEY-ID"},
|
||||
{ "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"},
|
||||
#endif
|
||||
{ "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Sign the commit with", "KEY_ID"},
|
||||
{ "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "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 },
|
||||
{ "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()", "POLICY" },
|
||||
@@ -185,13 +196,19 @@ commit_filter (OstreeRepo *self,
|
||||
g_file_info_set_attribute_uint32 (file_info, "unix::uid", opt_owner_uid);
|
||||
if (opt_owner_gid >= 0)
|
||||
g_file_info_set_attribute_uint32 (file_info, "unix::gid", opt_owner_gid);
|
||||
guint mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||||
|
||||
if (S_ISREG (mode) && opt_ro_executables && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
||||
{
|
||||
mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
|
||||
g_file_info_set_attribute_uint32 (file_info, "unix::mode", mode);
|
||||
}
|
||||
|
||||
if (mode_adds && g_hash_table_lookup_extended (mode_adds, path, NULL, &value))
|
||||
{
|
||||
guint current_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
|
||||
guint mode_add = GPOINTER_TO_UINT (value);
|
||||
g_file_info_set_attribute_uint32 (file_info, "unix::mode",
|
||||
current_mode | mode_add);
|
||||
mode | mode_add);
|
||||
g_hash_table_remove (mode_adds, path);
|
||||
}
|
||||
else if (mode_overrides && g_hash_table_lookup_extended (mode_overrides, path, NULL, &value))
|
||||
@@ -417,6 +434,7 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
OstreeRepoTransactionStats stats;
|
||||
struct CommitFilterData filter_data = { 0, };
|
||||
g_autofree char *commit_body = NULL;
|
||||
g_autoptr (OstreeSign) sign = NULL;
|
||||
|
||||
context = g_option_context_new ("[PATH]");
|
||||
|
||||
@@ -550,6 +568,11 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES;
|
||||
if (opt_disable_fsync)
|
||||
ostree_repo_set_disable_fsync (repo, TRUE);
|
||||
if (opt_selinux_policy && opt_selinux_policy_from_base)
|
||||
{
|
||||
glnx_throw (error, "Cannot specify both --selinux-policy and --selinux-policy-from-base");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (flags != 0
|
||||
|| opt_owner_uid >= 0
|
||||
@@ -557,25 +580,14 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
|| opt_statoverride_file != NULL
|
||||
|| opt_skiplist_file != NULL
|
||||
|| opt_no_xattrs
|
||||
|| opt_selinux_policy)
|
||||
|| opt_ro_executables
|
||||
|| opt_selinux_policy
|
||||
|| opt_selinux_policy_from_base)
|
||||
{
|
||||
filter_data.mode_adds = mode_adds;
|
||||
filter_data.skip_list = skip_list;
|
||||
modifier = ostree_repo_commit_modifier_new (flags, commit_filter,
|
||||
&filter_data, NULL);
|
||||
if (opt_selinux_policy)
|
||||
{
|
||||
glnx_autofd int rootfs_dfd = -1;
|
||||
if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error))
|
||||
{
|
||||
g_prefix_error (error, "selinux-policy: ");
|
||||
goto out;
|
||||
}
|
||||
policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error);
|
||||
if (!policy)
|
||||
goto out;
|
||||
ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_editor)
|
||||
@@ -599,129 +611,180 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
if (opt_link_checkout_speedup && !ostree_repo_scan_hardlinks (repo, cancellable, error))
|
||||
goto out;
|
||||
|
||||
mtree = ostree_mutable_tree_new ();
|
||||
|
||||
if (argc <= 1 && (opt_trees == NULL || opt_trees[0] == NULL))
|
||||
if (opt_base)
|
||||
{
|
||||
if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, modifier,
|
||||
cancellable, error))
|
||||
g_autofree char *base_commit = NULL;
|
||||
g_autoptr(GFile) root = NULL;
|
||||
if (!ostree_repo_read_commit (repo, opt_base, &root, &base_commit, cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else if (opt_trees != NULL)
|
||||
{
|
||||
const char *const*tree_iter;
|
||||
const char *tree;
|
||||
const char *eq;
|
||||
OstreeRepoFile *rootf = (OstreeRepoFile*) root;
|
||||
|
||||
for (tree_iter = (const char *const*)opt_trees; *tree_iter; tree_iter++)
|
||||
mtree = ostree_mutable_tree_new_from_checksum (repo,
|
||||
ostree_repo_file_tree_get_contents_checksum (rootf),
|
||||
ostree_repo_file_tree_get_metadata_checksum (rootf));
|
||||
|
||||
if (opt_selinux_policy_from_base)
|
||||
{
|
||||
tree = *tree_iter;
|
||||
|
||||
eq = strchr (tree, '=');
|
||||
if (!eq)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Missing type in tree specification '%s'", tree);
|
||||
goto out;
|
||||
}
|
||||
g_free (tree_type);
|
||||
tree_type = g_strndup (tree, eq - tree);
|
||||
tree = eq + 1;
|
||||
|
||||
g_clear_object (&object_to_commit);
|
||||
if (strcmp (tree_type, "dir") == 0)
|
||||
{
|
||||
if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, tree, mtree, modifier,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else if (strcmp (tree_type, "tar") == 0)
|
||||
{
|
||||
if (!opt_tar_pathname_filter)
|
||||
{
|
||||
if (strcmp (tree, "-") == 0)
|
||||
{
|
||||
if (!ostree_repo_write_archive_to_mtree_from_fd (repo, STDIN_FILENO, mtree, modifier,
|
||||
opt_tar_autocreate_parents,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
object_to_commit = g_file_new_for_path (tree);
|
||||
|
||||
if (!ostree_repo_write_archive_to_mtree (repo, object_to_commit, mtree, modifier,
|
||||
opt_tar_autocreate_parents,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_LIBARCHIVE
|
||||
const char *comma = strchr (opt_tar_pathname_filter, ',');
|
||||
if (!comma)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Missing ',' in --tar-pathname-filter");
|
||||
goto out;
|
||||
}
|
||||
const char *replacement = comma + 1;
|
||||
g_autofree char *regexp_text = g_strndup (opt_tar_pathname_filter, comma - opt_tar_pathname_filter);
|
||||
/* Use new API if we have a pathname filter */
|
||||
OstreeRepoImportArchiveOptions opts = { 0, };
|
||||
opts.autocreate_parents = opt_tar_autocreate_parents;
|
||||
opts.translate_pathname = handle_translate_pathname;
|
||||
g_autoptr(GRegex) regexp = g_regex_new (regexp_text, 0, 0, error);
|
||||
TranslatePathnameData tpdata = { regexp, replacement };
|
||||
if (!regexp)
|
||||
{
|
||||
g_prefix_error (error, "--tar-pathname-filter: ");
|
||||
goto out;
|
||||
}
|
||||
opts.translate_pathname_user_data = &tpdata;
|
||||
|
||||
g_autoptr(OtAutoArchiveRead) archive;
|
||||
if (strcmp (tree, "-") == 0)
|
||||
archive = ot_open_archive_read_fd (STDIN_FILENO, error);
|
||||
else
|
||||
archive = ot_open_archive_read (tree, error);
|
||||
|
||||
if (!archive)
|
||||
goto out;
|
||||
if (!ostree_repo_import_archive_to_mtree (repo, &opts, archive, mtree,
|
||||
modifier, cancellable, error))
|
||||
goto out;
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"This version of ostree is not compiled with libarchive support");
|
||||
goto out;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (strcmp (tree_type, "ref") == 0)
|
||||
{
|
||||
if (!ostree_repo_read_commit (repo, tree, &object_to_commit, NULL, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!ostree_repo_write_directory_to_mtree (repo, object_to_commit, mtree, modifier,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Invalid tree type specification '%s'", tree_type);
|
||||
goto out;
|
||||
}
|
||||
g_assert (modifier);
|
||||
if (!ostree_repo_commit_modifier_set_sepolicy_from_commit (modifier, repo, base_commit, cancellable, error))
|
||||
goto out;
|
||||
/* Don't try to handle it twice */
|
||||
opt_selinux_policy_from_base = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert (argc > 1);
|
||||
if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, argv[1], mtree, modifier,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
mtree = ostree_mutable_tree_new ();
|
||||
}
|
||||
|
||||
|
||||
/* Convert implicit . or explicit path via argv into
|
||||
* --tree=dir= so that we only have one primary code path below.
|
||||
*/
|
||||
if (opt_trees == NULL || opt_trees[0] == NULL)
|
||||
{
|
||||
char *path;
|
||||
if (argc <= 1)
|
||||
path = ".";
|
||||
else
|
||||
path = argv[1];
|
||||
opt_trees = g_new0 (char *, 2);
|
||||
opt_trees[0] = g_strconcat ("dir=", path, NULL);
|
||||
}
|
||||
|
||||
const char *const*tree_iter;
|
||||
const char *tree;
|
||||
const char *eq;
|
||||
g_assert (opt_trees && *opt_trees);
|
||||
for (tree_iter = (const char *const*)opt_trees; *tree_iter; tree_iter++)
|
||||
{
|
||||
const gboolean first = (tree_iter == (const char *const*)opt_trees);
|
||||
tree = *tree_iter;
|
||||
|
||||
eq = strchr (tree, '=');
|
||||
if (!eq)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Missing type in tree specification '%s'", tree);
|
||||
goto out;
|
||||
}
|
||||
g_free (tree_type);
|
||||
tree_type = g_strndup (tree, eq - tree);
|
||||
tree = eq + 1;
|
||||
|
||||
g_clear_object (&object_to_commit);
|
||||
if (strcmp (tree_type, "dir") == 0)
|
||||
{
|
||||
if (first && opt_selinux_policy_from_base)
|
||||
{
|
||||
opt_selinux_policy = g_strdup (tree);
|
||||
opt_selinux_policy_from_base = FALSE;
|
||||
}
|
||||
if (first && opt_selinux_policy)
|
||||
{
|
||||
g_assert (modifier);
|
||||
glnx_autofd int rootfs_dfd = -1;
|
||||
if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error))
|
||||
goto out;
|
||||
policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error);
|
||||
if (!policy)
|
||||
goto out;
|
||||
ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
|
||||
}
|
||||
if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, tree, mtree, modifier,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else if (strcmp (tree_type, "tar") == 0)
|
||||
{
|
||||
if (first && opt_selinux_policy_from_base)
|
||||
{
|
||||
glnx_throw (error, "Cannot use --selinux-policy-from-base with tar");
|
||||
goto out;
|
||||
}
|
||||
if (!opt_tar_pathname_filter)
|
||||
{
|
||||
if (strcmp (tree, "-") == 0)
|
||||
{
|
||||
if (!ostree_repo_write_archive_to_mtree_from_fd (repo, STDIN_FILENO, mtree, modifier,
|
||||
opt_tar_autocreate_parents,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
object_to_commit = g_file_new_for_path (tree);
|
||||
|
||||
if (!ostree_repo_write_archive_to_mtree (repo, object_to_commit, mtree, modifier,
|
||||
opt_tar_autocreate_parents,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef HAVE_LIBARCHIVE
|
||||
const char *comma = strchr (opt_tar_pathname_filter, ',');
|
||||
if (!comma)
|
||||
{
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Missing ',' in --tar-pathname-filter");
|
||||
goto out;
|
||||
}
|
||||
const char *replacement = comma + 1;
|
||||
g_autofree char *regexp_text = g_strndup (opt_tar_pathname_filter, comma - opt_tar_pathname_filter);
|
||||
/* Use new API if we have a pathname filter */
|
||||
OstreeRepoImportArchiveOptions opts = { 0, };
|
||||
opts.autocreate_parents = opt_tar_autocreate_parents;
|
||||
opts.translate_pathname = handle_translate_pathname;
|
||||
g_autoptr(GRegex) regexp = g_regex_new (regexp_text, 0, 0, error);
|
||||
TranslatePathnameData tpdata = { regexp, replacement };
|
||||
if (!regexp)
|
||||
{
|
||||
g_prefix_error (error, "--tar-pathname-filter: ");
|
||||
goto out;
|
||||
}
|
||||
opts.translate_pathname_user_data = &tpdata;
|
||||
|
||||
g_autoptr(OtAutoArchiveRead) archive;
|
||||
if (strcmp (tree, "-") == 0)
|
||||
archive = ot_open_archive_read_fd (STDIN_FILENO, error);
|
||||
else
|
||||
archive = ot_open_archive_read (tree, error);
|
||||
|
||||
if (!archive)
|
||||
goto out;
|
||||
if (!ostree_repo_import_archive_to_mtree (repo, &opts, archive, mtree,
|
||||
modifier, cancellable, error))
|
||||
goto out;
|
||||
#else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
"This version of ostree is not compiled with libarchive support");
|
||||
goto out;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (strcmp (tree_type, "ref") == 0)
|
||||
{
|
||||
if (first && opt_selinux_policy_from_base)
|
||||
{
|
||||
g_assert (modifier);
|
||||
if (!ostree_repo_commit_modifier_set_sepolicy_from_commit (modifier, repo, tree, cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
if (!ostree_repo_read_commit (repo, tree, &object_to_commit, NULL, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (!ostree_repo_write_directory_to_mtree (repo, object_to_commit, mtree, modifier,
|
||||
cancellable, error))
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Invalid tree type specification '%s'", tree_type);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode_adds && g_hash_table_size (mode_adds) > 0)
|
||||
@@ -811,12 +874,41 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
goto out;
|
||||
}
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (opt_key_ids)
|
||||
{
|
||||
/* Initialize crypto system */
|
||||
opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
|
||||
|
||||
sign = ostree_sign_get_by_name (opt_sign_name, error);
|
||||
if (sign == NULL)
|
||||
goto out;
|
||||
|
||||
char **iter;
|
||||
|
||||
for (iter = opt_key_ids; iter && *iter; iter++)
|
||||
{
|
||||
const char *keyid = *iter;
|
||||
g_autoptr (GVariant) secret_key = NULL;
|
||||
|
||||
secret_key = g_variant_new_string (keyid);
|
||||
if (!ostree_sign_set_sk (sign, secret_key, error))
|
||||
goto out;
|
||||
|
||||
if (!ostree_sign_commit (sign,
|
||||
repo,
|
||||
commit_checksum,
|
||||
cancellable,
|
||||
error))
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (opt_gpg_key_ids)
|
||||
{
|
||||
char **iter;
|
||||
|
||||
for (iter = opt_gpg_key_ids; iter && *iter; iter++)
|
||||
{
|
||||
const char *keyid = *iter;
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ ostree_builtin_diff (int argc, char **argv, OstreeCommandInvocation *invocation,
|
||||
g_autoptr(GPtrArray) removed = NULL;
|
||||
g_autoptr(GPtrArray) added = NULL;
|
||||
|
||||
context = g_option_context_new ("REV TARGETDIR");
|
||||
context = g_option_context_new ("REV_OR_DIR REV_OR_DIR");
|
||||
|
||||
if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
|
||||
goto out;
|
||||
|
||||
@@ -35,6 +35,7 @@ static gchar *opt_cache_dir = NULL;
|
||||
static gchar *opt_finders = NULL;
|
||||
static gboolean opt_disable_fsync = FALSE;
|
||||
static gboolean opt_pull = FALSE;
|
||||
static gboolean opt_mirror = FALSE;
|
||||
|
||||
static GOptionEntry options[] =
|
||||
{
|
||||
@@ -42,6 +43,7 @@ static GOptionEntry options[] =
|
||||
{ "disable-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_disable_fsync, "Do not invoke fsync()", NULL },
|
||||
{ "finders", 0, 0, G_OPTION_ARG_STRING, &opt_finders, "Use the specified comma separated list of finders (e.g. config,lan,mount)", "FINDERS" },
|
||||
{ "pull", 0, 0, G_OPTION_ARG_NONE, &opt_pull, "Pull the updates after finding them", NULL },
|
||||
{ "mirror", 0, 0, G_OPTION_ARG_NONE, &opt_mirror, "Do a mirror pull (see ostree pull --mirror)", NULL},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
@@ -188,6 +190,7 @@ ostree_builtin_find_remotes (int argc,
|
||||
g_auto(OstreeRepoFinderResultv) results = NULL;
|
||||
g_auto(GLnxConsoleRef) console = { 0, };
|
||||
g_autoptr(GHashTable) refs_found = NULL; /* set (element-type OstreeCollectionRef) */
|
||||
g_autoptr(GVariant) pull_options = NULL;
|
||||
|
||||
context = g_option_context_new ("COLLECTION-ID REF [COLLECTION-ID REF...]");
|
||||
|
||||
@@ -210,6 +213,12 @@ ostree_builtin_find_remotes (int argc,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (opt_mirror && !opt_pull)
|
||||
{
|
||||
ot_util_usage_error (context, "When --mirror is specified, --pull must also be", error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (opt_disable_fsync)
|
||||
ostree_repo_set_disable_fsync (repo, TRUE);
|
||||
|
||||
@@ -359,13 +368,24 @@ ostree_builtin_find_remotes (int argc,
|
||||
if (!opt_pull)
|
||||
return TRUE;
|
||||
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
||||
|
||||
if (opt_mirror)
|
||||
g_variant_builder_add (&builder, "{s@v}", "flags",
|
||||
g_variant_new_variant (g_variant_new_int32 (OSTREE_REPO_PULL_FLAGS_MIRROR)));
|
||||
|
||||
pull_options = g_variant_ref_sink (g_variant_builder_end (&builder));
|
||||
}
|
||||
|
||||
/* Run the pull operation. */
|
||||
if (console.is_tty)
|
||||
progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console);
|
||||
|
||||
ostree_repo_pull_from_remotes_async (repo,
|
||||
(const OstreeRepoFinderResult * const *) results,
|
||||
NULL, /* no options */
|
||||
pull_options,
|
||||
progress, cancellable,
|
||||
get_result_cb, &pull_result);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
static char *opt_remote;
|
||||
static gboolean opt_commit_only;
|
||||
static gboolean opt_disable_fsync;
|
||||
static gboolean opt_per_object_fsync;
|
||||
static gboolean opt_untrusted;
|
||||
static gboolean opt_bareuseronly_files;
|
||||
static gboolean opt_require_static_deltas;
|
||||
@@ -50,6 +51,7 @@ static GOptionEntry options[] = {
|
||||
{ "commit-metadata-only", 0, 0, G_OPTION_ARG_NONE, &opt_commit_only, "Fetch only the commit metadata", NULL },
|
||||
{ "remote", 0, 0, G_OPTION_ARG_STRING, &opt_remote, "Add REMOTE to refspec", "REMOTE" },
|
||||
{ "disable-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_disable_fsync, "Do not invoke fsync()", NULL },
|
||||
{ "per-object-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_per_object_fsync, "Perform writes in such a way that avoids stalling concurrent processes", NULL },
|
||||
{ "untrusted", 0, 0, G_OPTION_ARG_NONE, &opt_untrusted, "Verify checksums of local sources (always enabled for HTTP pulls)", NULL },
|
||||
{ "bareuseronly-files", 0, 0, G_OPTION_ARG_NONE, &opt_bareuseronly_files, "Reject regular files with mode outside of 0775 (world writable, suid, etc.)", NULL },
|
||||
{ "require-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_require_static_deltas, "Require static deltas", NULL },
|
||||
@@ -181,6 +183,16 @@ ostree_builtin_pull_local (int argc, char **argv, OstreeCommandInvocation *invoc
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
g_variant_builder_add (&builder, "{s@v}", "depth",
|
||||
g_variant_new_variant (g_variant_new_int32 (opt_depth)));
|
||||
/* local pulls always disable signapi verification. If you don't want this, use
|
||||
* ostree remote add --sign-verify=<key> file://
|
||||
*/
|
||||
g_variant_builder_add (&builder, "{s@v}", "disable-sign-verify",
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
g_variant_builder_add (&builder, "{s@v}", "disable-sign-verify-summary",
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
if (opt_per_object_fsync)
|
||||
g_variant_builder_add (&builder, "{s@v}", "per-object-fsync",
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
|
||||
if (console.is_tty)
|
||||
progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "otutil.h"
|
||||
|
||||
static gboolean opt_disable_fsync;
|
||||
static gboolean opt_per_object_fsync;
|
||||
static gboolean opt_mirror;
|
||||
static gboolean opt_commit_only;
|
||||
static gboolean opt_dry_run;
|
||||
@@ -37,6 +38,7 @@ static gboolean opt_require_static_deltas;
|
||||
static gboolean opt_untrusted;
|
||||
static gboolean opt_http_trusted;
|
||||
static gboolean opt_timestamp_check;
|
||||
static char* opt_timestamp_check_from_rev;
|
||||
static gboolean opt_bareuseronly_files;
|
||||
static char** opt_subpaths;
|
||||
static char** opt_http_headers;
|
||||
@@ -57,6 +59,7 @@ static GOptionEntry options[] = {
|
||||
{ "commit-metadata-only", 0, 0, G_OPTION_ARG_NONE, &opt_commit_only, "Fetch only the commit metadata", NULL },
|
||||
{ "cache-dir", 0, 0, G_OPTION_ARG_FILENAME, &opt_cache_dir, "Use custom cache dir", NULL },
|
||||
{ "disable-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_disable_fsync, "Do not invoke fsync()", NULL },
|
||||
{ "per-object-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_per_object_fsync, "Perform writes in such a way that avoids stalling concurrent processes", NULL },
|
||||
{ "disable-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_disable_static_deltas, "Do not use static deltas", NULL },
|
||||
{ "require-static-deltas", 0, 0, G_OPTION_ARG_NONE, &opt_require_static_deltas, "Require static deltas", NULL },
|
||||
{ "mirror", 0, 0, G_OPTION_ARG_NONE, &opt_mirror, "Write refs suitable for a mirror and fetches all refs if none provided", NULL },
|
||||
@@ -72,6 +75,7 @@ static GOptionEntry options[] = {
|
||||
{ "network-retries", 0, 0, G_OPTION_ARG_INT, &opt_network_retries, "Specifies how many times each download should be retried upon error (default: 5)", "N"},
|
||||
{ "localcache-repo", 'L', 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_localcache_repos, "Add REPO as local cache source for objects during this pull", "REPO" },
|
||||
{ "timestamp-check", 'T', 0, G_OPTION_ARG_NONE, &opt_timestamp_check, "Require fetched commits to have newer timestamps", NULL },
|
||||
{ "timestamp-check-from-rev", 0, 0, G_OPTION_ARG_STRING, &opt_timestamp_check_from_rev, "Require fetched commits to have newer timestamps than given rev", NULL },
|
||||
/* let's leave this hidden for now; we just need it for tests */
|
||||
{ "append-user-agent", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_append_user_agent, "Append string to user agent", NULL },
|
||||
{ NULL }
|
||||
@@ -313,6 +317,9 @@ ostree_builtin_pull (int argc, char **argv, OstreeCommandInvocation *invocation,
|
||||
if (opt_timestamp_check)
|
||||
g_variant_builder_add (&builder, "{s@v}", "timestamp-check",
|
||||
g_variant_new_variant (g_variant_new_boolean (opt_timestamp_check)));
|
||||
if (opt_timestamp_check_from_rev)
|
||||
g_variant_builder_add (&builder, "{s@v}", "timestamp-check-from-rev",
|
||||
g_variant_new_variant (g_variant_new_string (opt_timestamp_check_from_rev)));
|
||||
|
||||
if (override_commit_ids)
|
||||
g_variant_builder_add (&builder, "{s@v}", "override-commit-ids",
|
||||
@@ -320,7 +327,9 @@ ostree_builtin_pull (int argc, char **argv, OstreeCommandInvocation *invocation,
|
||||
if (opt_localcache_repos)
|
||||
g_variant_builder_add (&builder, "{s@v}", "localcache-repos",
|
||||
g_variant_new_variant (g_variant_new_strv ((const char*const*)opt_localcache_repos, -1)));
|
||||
|
||||
if (opt_per_object_fsync)
|
||||
g_variant_builder_add (&builder, "{s@v}", "per-object-fsync",
|
||||
g_variant_new_variant (g_variant_new_boolean (TRUE)));
|
||||
if (opt_http_headers)
|
||||
{
|
||||
GVariantBuilder hdr_builder;
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/* vim:set et sw=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e2s: */
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 Colin Walters <walters@verbum.org>
|
||||
* Copyright (C) 2019 Denis Pynkin (d4s) <denis.pynkin@collabora.com>
|
||||
*
|
||||
* 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, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Author: Colin Walters <walters@verbum.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "ot-main.h"
|
||||
#include "ot-builtins.h"
|
||||
#include "ostree.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-sign.h"
|
||||
|
||||
static gboolean opt_delete;
|
||||
static gboolean opt_verify;
|
||||
static char *opt_sign_name;
|
||||
static char *opt_filename;
|
||||
static char *opt_keysdir;
|
||||
|
||||
/* ATTENTION:
|
||||
* Please remember to update the bash-completion script (bash/ostree) and
|
||||
* man page (man/ostree-sign.xml) when changing the option list.
|
||||
*/
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ "delete", 'd', 0, G_OPTION_ARG_NONE, &opt_delete, "Delete signatures having any of the KEY-IDs", NULL},
|
||||
{ "verify", 0, 0, G_OPTION_ARG_NONE, &opt_verify, "Verify signatures", NULL},
|
||||
{ "sign-type", 's', 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"},
|
||||
#if defined(HAVE_LIBSODIUM)
|
||||
{ "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_filename, "Read key(s) from file", "NAME"},
|
||||
{ "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"},
|
||||
#endif
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
usage_error (GOptionContext *context, const char *message, GError **error)
|
||||
{
|
||||
g_autofree char *help = g_option_context_get_help (context, TRUE, NULL);
|
||||
g_printerr ("%s", help);
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, message);
|
||||
}
|
||||
|
||||
gboolean
|
||||
ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
g_autoptr (GOptionContext) context = NULL;
|
||||
g_autoptr (OstreeRepo) repo = NULL;
|
||||
g_autoptr (OstreeSign) sign = NULL;
|
||||
g_autofree char *resolved_commit = NULL;
|
||||
g_autofree char *success_message = NULL;
|
||||
const char *commit;
|
||||
char **key_ids;
|
||||
int n_key_ids, ii;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
context = g_option_context_new ("COMMIT KEY-ID...");
|
||||
|
||||
|
||||
if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
|
||||
goto out;
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
usage_error (context, "Need a COMMIT to sign or verify", error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
commit = argv[1];
|
||||
|
||||
/* Verification could be done via system files with public keys */
|
||||
if (!opt_verify &&
|
||||
!opt_filename &&
|
||||
argc < 3)
|
||||
{
|
||||
usage_error (context, "Need at least one KEY-ID to sign with", error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
key_ids = argv + 2;
|
||||
n_key_ids = argc - 2;
|
||||
|
||||
if (!ostree_repo_resolve_rev (repo, commit, FALSE, &resolved_commit, error))
|
||||
goto out;
|
||||
|
||||
/* Initialize crypto system */
|
||||
opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
|
||||
|
||||
sign = ostree_sign_get_by_name (opt_sign_name, error);
|
||||
if (sign == NULL)
|
||||
goto out;
|
||||
|
||||
for (ii = 0; ii < n_key_ids; ii++)
|
||||
{
|
||||
g_autoptr (GVariant) sk = NULL;
|
||||
g_autoptr (GVariant) pk = NULL;
|
||||
|
||||
if (opt_verify)
|
||||
{
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
|
||||
|
||||
// Pass the key as a string
|
||||
pk = g_variant_new_string(key_ids[ii]);
|
||||
|
||||
if (!ostree_sign_set_pk (sign, pk, &local_error))
|
||||
continue;
|
||||
|
||||
if (ostree_sign_commit_verify (sign,
|
||||
repo,
|
||||
resolved_commit,
|
||||
&success_message,
|
||||
cancellable,
|
||||
&local_error))
|
||||
{
|
||||
g_assert (success_message);
|
||||
g_print ("%s\n", success_message);
|
||||
ret = TRUE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass the key as a string
|
||||
sk = g_variant_new_string(key_ids[ii]);
|
||||
if (!ostree_sign_set_sk (sign, sk, error))
|
||||
{
|
||||
ret = FALSE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = ostree_sign_commit (sign,
|
||||
repo,
|
||||
resolved_commit,
|
||||
cancellable,
|
||||
error);
|
||||
if (ret != TRUE)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to verify with user-provided file or system configuration */
|
||||
if (opt_verify)
|
||||
{
|
||||
if ((n_key_ids == 0) || opt_filename)
|
||||
{
|
||||
g_autoptr (GVariantBuilder) builder = NULL;
|
||||
g_autoptr (GVariant) options = NULL;
|
||||
|
||||
builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
|
||||
/* Use custom directory with public and revoked keys instead of system-wide directories */
|
||||
if (opt_keysdir)
|
||||
g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir));
|
||||
/* The last chance for verification source -- system files */
|
||||
if (opt_filename)
|
||||
g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_filename));
|
||||
options = g_variant_builder_end (builder);
|
||||
|
||||
if (!ostree_sign_load_pk (sign, options, error))
|
||||
goto out;
|
||||
|
||||
if (ostree_sign_commit_verify (sign,
|
||||
repo,
|
||||
resolved_commit,
|
||||
&success_message,
|
||||
cancellable,
|
||||
error))
|
||||
{
|
||||
g_print ("%s\n", success_message);
|
||||
ret = TRUE;
|
||||
}
|
||||
} /* Check via file */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Sign with keys from provided file */
|
||||
if (opt_filename)
|
||||
{
|
||||
g_autoptr (GFile) keyfile = NULL;
|
||||
g_autoptr (GFileInputStream) key_stream_in = NULL;
|
||||
g_autoptr (GDataInputStream) key_data_in = NULL;
|
||||
|
||||
if (!g_file_test (opt_filename, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
g_warning ("Can't open file '%s' with keys", opt_filename);
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"File object '%s' is not a regular file", opt_filename);
|
||||
goto out;
|
||||
}
|
||||
|
||||
keyfile = g_file_new_for_path (opt_filename);
|
||||
key_stream_in = g_file_read (keyfile, NULL, error);
|
||||
if (key_stream_in == NULL)
|
||||
goto out;
|
||||
|
||||
key_data_in = g_data_input_stream_new (G_INPUT_STREAM(key_stream_in));
|
||||
g_assert (key_data_in != NULL);
|
||||
|
||||
/* Use simple file format with just a list of base64 public keys per line */
|
||||
while (TRUE)
|
||||
{
|
||||
gsize len = 0;
|
||||
g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error);
|
||||
g_autoptr (GVariant) sk = NULL;
|
||||
|
||||
if (*error != NULL)
|
||||
goto out;
|
||||
|
||||
if (line == NULL)
|
||||
break;
|
||||
|
||||
|
||||
// Pass the key as a string
|
||||
sk = g_variant_new_string(line);
|
||||
if (!ostree_sign_set_sk (sign, sk, error))
|
||||
{
|
||||
ret = FALSE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = ostree_sign_commit (sign,
|
||||
repo,
|
||||
resolved_commit,
|
||||
cancellable,
|
||||
error);
|
||||
if (ret != TRUE)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No valid signature found
|
||||
if (opt_verify && (ret != TRUE) && (*error == NULL))
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No valid signatures found");
|
||||
|
||||
out:
|
||||
/* It is possible to have an error due multiple signatures check */
|
||||
if (ret == TRUE)
|
||||
g_clear_error (error);
|
||||
return ret;
|
||||
}
|
||||
@@ -27,10 +27,13 @@
|
||||
#include "ot-builtins.h"
|
||||
#include "ostree.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-sign.h"
|
||||
|
||||
static gboolean opt_update, opt_view, opt_raw;
|
||||
static char **opt_key_ids;
|
||||
static char **opt_gpg_key_ids;
|
||||
static char *opt_gpg_homedir;
|
||||
static char **opt_key_ids;
|
||||
static char *opt_sign_name;
|
||||
static char **opt_metadata;
|
||||
|
||||
/* ATTENTION:
|
||||
@@ -42,8 +45,10 @@ static GOptionEntry options[] = {
|
||||
{ "update", 'u', 0, G_OPTION_ARG_NONE, &opt_update, "Update the summary", NULL },
|
||||
{ "view", 'v', 0, G_OPTION_ARG_NONE, &opt_view, "View the local summary file", NULL },
|
||||
{ "raw", 0, 0, G_OPTION_ARG_NONE, &opt_raw, "View the raw bytes of the summary file", NULL },
|
||||
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the summary with", "KEY-ID"},
|
||||
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_gpg_key_ids, "GPG Key ID to sign the summary with", "KEY-ID"},
|
||||
{ "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"},
|
||||
{ "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Key ID to sign the summary with", "KEY-ID"},
|
||||
{ "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"},
|
||||
{ "add-metadata", 'm', 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata, "Additional metadata field to add to the summary", "KEY=VALUE" },
|
||||
{ NULL }
|
||||
};
|
||||
@@ -87,6 +92,7 @@ ostree_builtin_summary (int argc, char **argv, OstreeCommandInvocation *invocati
|
||||
{
|
||||
g_autoptr(GOptionContext) context = NULL;
|
||||
g_autoptr(OstreeRepo) repo = NULL;
|
||||
g_autoptr (OstreeSign) sign = NULL;
|
||||
OstreeDumpFlags flags = OSTREE_DUMP_NONE;
|
||||
|
||||
context = g_option_context_new ("");
|
||||
@@ -94,6 +100,16 @@ ostree_builtin_summary (int argc, char **argv, OstreeCommandInvocation *invocati
|
||||
if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* Initialize crypto system */
|
||||
if (opt_key_ids)
|
||||
{
|
||||
opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
|
||||
|
||||
sign = ostree_sign_get_by_name (opt_sign_name, error);
|
||||
if (sign == NULL)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (opt_update)
|
||||
{
|
||||
g_autoptr(GVariant) additional_metadata = NULL;
|
||||
@@ -164,10 +180,9 @@ ostree_builtin_summary (int argc, char **argv, OstreeCommandInvocation *invocati
|
||||
new_summary_commit, repo_file, &new_ostree_metadata_checksum,
|
||||
NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (opt_key_ids != NULL)
|
||||
if (opt_gpg_key_ids != NULL)
|
||||
{
|
||||
for (const char * const *iter = (const char * const *) opt_key_ids;
|
||||
for (const char * const *iter = (const char * const *) opt_gpg_key_ids;
|
||||
iter != NULL && *iter != NULL; iter++)
|
||||
{
|
||||
const char *key_id = *iter;
|
||||
@@ -182,6 +197,27 @@ ostree_builtin_summary (int argc, char **argv, OstreeCommandInvocation *invocati
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_key_ids)
|
||||
{
|
||||
char **iter;
|
||||
for (iter = opt_key_ids; iter && *iter; iter++)
|
||||
{
|
||||
const char *keyid = *iter;
|
||||
g_autoptr (GVariant) secret_key = NULL;
|
||||
|
||||
secret_key = g_variant_new_string (keyid);
|
||||
if (!ostree_sign_set_sk (sign, secret_key, error))
|
||||
return FALSE;
|
||||
|
||||
if (!ostree_sign_commit (sign,
|
||||
repo,
|
||||
new_ostree_metadata_checksum,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
ostree_repo_transaction_set_collection_ref (repo, &collection_ref,
|
||||
new_ostree_metadata_checksum);
|
||||
|
||||
@@ -194,16 +230,45 @@ ostree_builtin_summary (int argc, char **argv, OstreeCommandInvocation *invocati
|
||||
return FALSE;
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (opt_key_ids)
|
||||
if (opt_gpg_key_ids)
|
||||
{
|
||||
if (!ostree_repo_add_gpg_signature_summary (repo,
|
||||
(const gchar **) opt_key_ids,
|
||||
(const gchar **) opt_gpg_key_ids,
|
||||
opt_gpg_homedir,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
if (opt_key_ids)
|
||||
{
|
||||
g_autoptr (GVariant) secret_keys = NULL;
|
||||
g_autoptr (GVariantBuilder) sk_builder = NULL;
|
||||
|
||||
sk_builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
|
||||
|
||||
char **iter;
|
||||
for (iter = opt_key_ids; iter && *iter; iter++)
|
||||
{
|
||||
const char *keyid = *iter;
|
||||
GVariant *secret_key = NULL;
|
||||
|
||||
/* Currently only strings are used as keys
|
||||
* for supported signature types */
|
||||
secret_key = g_variant_new_string (keyid);
|
||||
|
||||
g_variant_builder_add (sk_builder, "v", secret_key);
|
||||
}
|
||||
|
||||
secret_keys = g_variant_builder_end (sk_builder);
|
||||
|
||||
if (! ostree_sign_summary (sign,
|
||||
repo,
|
||||
secret_keys,
|
||||
cancellable,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (opt_view || opt_raw)
|
||||
{
|
||||
|
||||
@@ -53,6 +53,7 @@ BUILTINPROTO(prune);
|
||||
BUILTINPROTO(refs);
|
||||
BUILTINPROTO(reset);
|
||||
BUILTINPROTO(fsck);
|
||||
BUILTINPROTO(sign);
|
||||
BUILTINPROTO(show);
|
||||
BUILTINPROTO(static_delta);
|
||||
BUILTINPROTO(summary);
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
|
||||
static char **opt_set;
|
||||
static gboolean opt_no_gpg_verify;
|
||||
static gboolean opt_no_sign_verify;
|
||||
static gboolean opt_if_not_exists;
|
||||
static gboolean opt_force;
|
||||
static char *opt_gpg_import;
|
||||
static char **opt_sign_verify;
|
||||
static char *opt_contenturl;
|
||||
static char *opt_collection_id;
|
||||
static char *opt_sysroot;
|
||||
@@ -44,6 +46,8 @@ static char *opt_repo;
|
||||
static GOptionEntry option_entries[] = {
|
||||
{ "set", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_set, "Set config option KEY=VALUE for remote", "KEY=VALUE" },
|
||||
{ "no-gpg-verify", 0, 0, G_OPTION_ARG_NONE, &opt_no_gpg_verify, "Disable GPG verification", NULL },
|
||||
{ "no-sign-verify", 0, 0, G_OPTION_ARG_NONE, &opt_no_sign_verify, "Disable signature verification", NULL },
|
||||
{ "sign-verify", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_sign_verify, "Verify signatures using KEYTYPE=inline:PUBKEY or KEYTYPE=file:/path/to/key", "KEYTYPE=[inline|file]:PUBKEY" },
|
||||
{ "if-not-exists", 0, 0, G_OPTION_ARG_NONE, &opt_if_not_exists, "Do nothing if the provided remote exists", NULL },
|
||||
{ "force", 0, 0, G_OPTION_ARG_NONE, &opt_force, "Replace the provided remote if it exists", NULL },
|
||||
{ "gpg-import", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_import, "Import GPG key from FILE", "FILE" },
|
||||
@@ -55,12 +59,49 @@ static GOptionEntry option_entries[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static char *
|
||||
add_verify_opt (GVariantBuilder *builder,
|
||||
const char *keyspec,
|
||||
GError **error)
|
||||
{
|
||||
g_auto(GStrv) parts = g_strsplit (keyspec, "=", 2);
|
||||
g_assert (parts && *parts);
|
||||
const char *keytype = parts[0];
|
||||
if (!parts[1])
|
||||
return glnx_null_throw (error, "Failed to parse KEYTYPE=[inline|file]:DATA in %s", keyspec);
|
||||
|
||||
g_autoptr(OstreeSign) sign = ostree_sign_get_by_name (keytype, error);
|
||||
if (!sign)
|
||||
return NULL;
|
||||
|
||||
const char *rest = parts[1];
|
||||
g_assert (!parts[2]);
|
||||
g_auto(GStrv) keyparts = g_strsplit (rest, ":", 2);
|
||||
g_assert (keyparts && *keyparts);
|
||||
const char *keyref = keyparts[0];
|
||||
g_assert (keyref);
|
||||
g_autofree char *optname = NULL;
|
||||
if (g_str_equal (keyref, "inline"))
|
||||
optname = g_strdup_printf ("verification-%s-key", keytype);
|
||||
else if (g_str_equal (keyref, "file"))
|
||||
optname = g_strdup_printf ("verification-%s-file", keytype);
|
||||
else
|
||||
return glnx_null_throw (error, "Invalid key reference %s, expected inline|file", keyref);
|
||||
|
||||
g_assert (keyparts[1] && !keyparts[2]);
|
||||
g_variant_builder_add (builder, "{s@v}",
|
||||
optname,
|
||||
g_variant_new_variant (g_variant_new_string (keyparts[1])));
|
||||
return g_strdup (ostree_sign_get_name (sign));
|
||||
}
|
||||
|
||||
gboolean
|
||||
ot_remote_builtin_add (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
|
||||
{
|
||||
g_autoptr(GOptionContext) context = NULL;
|
||||
g_autoptr(OstreeSysroot) sysroot = NULL;
|
||||
g_autoptr(OstreeRepo) repo = NULL;
|
||||
g_autoptr(GString) sign_verify = NULL;
|
||||
const char *remote_name;
|
||||
const char *remote_url;
|
||||
char **iter;
|
||||
@@ -134,12 +175,43 @@ ot_remote_builtin_add (int argc, char **argv, OstreeCommandInvocation *invocatio
|
||||
}
|
||||
|
||||
#ifndef OSTREE_DISABLE_GPGME
|
||||
if (opt_no_gpg_verify)
|
||||
/* No signature verification implies no verification for GPG signature as well */
|
||||
if (opt_no_gpg_verify || opt_no_sign_verify)
|
||||
g_variant_builder_add (optbuilder, "{s@v}",
|
||||
"gpg-verify",
|
||||
g_variant_new_variant (g_variant_new_boolean (FALSE)));
|
||||
#endif /* OSTREE_DISABLE_GPGME */
|
||||
|
||||
if (opt_no_sign_verify)
|
||||
{
|
||||
if (opt_sign_verify)
|
||||
return glnx_throw (error, "Cannot specify both --sign-verify and --no-sign-verify");
|
||||
g_variant_builder_add (optbuilder, "{s@v}",
|
||||
"sign-verify",
|
||||
g_variant_new_variant (g_variant_new_boolean (FALSE)));
|
||||
}
|
||||
|
||||
for (char **iter = opt_sign_verify; iter && *iter; iter++)
|
||||
{
|
||||
const char *keyspec = *iter;
|
||||
g_autofree char *signname = add_verify_opt (optbuilder, keyspec, error);
|
||||
if (!signname)
|
||||
return FALSE;
|
||||
if (!sign_verify)
|
||||
{
|
||||
sign_verify = g_string_new (signname);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_string_append_c (sign_verify, ',');
|
||||
g_string_append (sign_verify, signname);
|
||||
}
|
||||
}
|
||||
if (sign_verify != NULL)
|
||||
g_variant_builder_add (optbuilder, "{s@v}",
|
||||
"sign-verify",
|
||||
g_variant_new_variant (g_variant_new_string (sign_verify->str)));
|
||||
|
||||
if (opt_collection_id != NULL)
|
||||
g_variant_builder_add (optbuilder, "{s@v}", "collection-id",
|
||||
g_variant_new_variant (g_variant_new_take_string (g_steal_pointer (&opt_collection_id))));
|
||||
|
||||
@@ -30,11 +30,13 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define INITRAMFS_MOUNT_VAR "/run/ostree/initramfs-mount-var"
|
||||
#define _OSTREE_SYSROOT_READONLY_STAMP "/run/ostree-sysroot-ro.stamp"
|
||||
|
||||
static inline int
|
||||
path_is_on_readonly_fs (char *path)
|
||||
path_is_on_readonly_fs (const char *path)
|
||||
{
|
||||
struct statvfs stvfsbuf;
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include <sys/syscall.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
@@ -83,6 +84,47 @@
|
||||
/* Initialized early in main */
|
||||
static bool running_as_pid1;
|
||||
|
||||
static inline bool
|
||||
sysroot_is_configured_ro (const char *sysroot)
|
||||
{
|
||||
char * config_path = NULL;
|
||||
assert (asprintf (&config_path, "%s/ostree/repo/config", sysroot) != -1);
|
||||
FILE *f = fopen(config_path, "r");
|
||||
if (!f)
|
||||
{
|
||||
fprintf (stderr, "Missing expected repo config: %s\n", config_path);
|
||||
free (config_path);
|
||||
return false;
|
||||
}
|
||||
free (config_path);
|
||||
|
||||
bool ret = false;
|
||||
char *line = NULL;
|
||||
size_t len = 0;
|
||||
ssize_t nread;
|
||||
/* Note getline() will reuse the previous buffer */
|
||||
bool in_sysroot = false;
|
||||
while ((nread = getline (&line, &len, f)) != -1)
|
||||
{
|
||||
/* This is an awful hack to avoid depending on GLib in the
|
||||
* initramfs right now.
|
||||
*/
|
||||
if (strstr (line, "[sysroot]") == line)
|
||||
in_sysroot = true;
|
||||
else if (*line == '[')
|
||||
in_sysroot = false;
|
||||
else if (in_sysroot && strstr (line, "readonly=true") == line)
|
||||
{
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose (f);
|
||||
free (line);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char*
|
||||
resolve_deploy_path (const char * root_mountpoint)
|
||||
{
|
||||
@@ -192,6 +234,33 @@ main(int argc, char *argv[])
|
||||
if (chdir (deploy_path) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to deploy_path");
|
||||
|
||||
/* Query the repository configuration - this is an operating system builder
|
||||
* choice. More info: https://github.com/ostreedev/ostree/pull/1767
|
||||
*/
|
||||
const bool sysroot_readonly = sysroot_is_configured_ro (root_arg);
|
||||
const bool sysroot_currently_writable = !path_is_on_readonly_fs (root_arg);
|
||||
|
||||
#ifdef USE_LIBSYSTEMD
|
||||
sd_journal_send ("MESSAGE=sysroot configured read-only: %d, currently writable: %d",
|
||||
(int)sysroot_readonly, (int)sysroot_currently_writable, NULL);
|
||||
#endif
|
||||
if (sysroot_readonly)
|
||||
{
|
||||
if (!sysroot_currently_writable)
|
||||
errx (EXIT_FAILURE, "sysroot=readonly currently requires writable / in initramfs");
|
||||
/* Now, /etc is not normally a bind mount, but if we have a readonly
|
||||
* sysroot, we still need a writable /etc. And to avoid race conditions
|
||||
* we ensure it's writable in the initramfs, before we switchroot at all.
|
||||
*/
|
||||
if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to make /etc a bind mount");
|
||||
/* Pass on the fact that we discovered a readonly sysroot to ostree-remount.service */
|
||||
int fd = open (_OSTREE_SYSROOT_READONLY_STAMP, O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
|
||||
if (fd < 0)
|
||||
err (EXIT_FAILURE, "failed to create %s", _OSTREE_SYSROOT_READONLY_STAMP);
|
||||
(void) close (fd);
|
||||
}
|
||||
|
||||
/* Default to true, but in the systemd case, default to false because it's handled by
|
||||
* ostree-system-generator. */
|
||||
bool mount_var = true;
|
||||
|
||||
@@ -81,21 +81,6 @@ do_remount (const char *target,
|
||||
printf ("Remounted %s: %s\n", writable ? "rw" : "ro", target);
|
||||
}
|
||||
|
||||
static bool
|
||||
sysroot_is_configured_ro (void)
|
||||
{
|
||||
struct stat stbuf;
|
||||
static const char config_path[] = "/ostree/repo/config";
|
||||
if (stat (config_path, &stbuf) != 0)
|
||||
return false;
|
||||
|
||||
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
|
||||
if (!g_key_file_load_from_file (keyfile, config_path, 0, NULL))
|
||||
return false;
|
||||
|
||||
return g_key_file_get_boolean (keyfile, "sysroot", "readonly", NULL);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
@@ -121,25 +106,10 @@ main(int argc, char *argv[])
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Query the repository configuration - this is an operating system builder
|
||||
* choice.
|
||||
* */
|
||||
const bool sysroot_readonly = sysroot_is_configured_ro ();
|
||||
|
||||
/* Mount the sysroot read-only if we're configured to do so.
|
||||
* Note we only get here if / is already writable.
|
||||
*/
|
||||
do_remount ("/sysroot", !sysroot_readonly);
|
||||
|
||||
if (sysroot_readonly)
|
||||
/* Handle remounting /sysroot read-only now */
|
||||
if (unlink (_OSTREE_SYSROOT_READONLY_STAMP) == 0)
|
||||
{
|
||||
/* Now, /etc is not normally a bind mount, but remounting the
|
||||
* sysroot above made it read-only since it's on the same filesystem.
|
||||
* Make it a self-bind mount, so we can then mount it read-write.
|
||||
*/
|
||||
if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to make /etc a bind mount");
|
||||
do_remount ("/etc", true);
|
||||
do_remount ("/sysroot", false);
|
||||
}
|
||||
|
||||
/* If /var was created as as an OSTree default bind mount (instead of being a separate filesystem)
|
||||
|
||||
Reference in new issue
Block a user