diff --git a/Makefile-libostree.am b/Makefile-libostree.am
index a180e86b..1d31c4d8 100644
--- a/Makefile-libostree.am
+++ b/Makefile-libostree.am
@@ -183,7 +183,7 @@ endif # USE_GPGME
symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym
#if BUILDOPT_IS_DEVEL_BUILD
-#symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
+symbol_files += $(top_srcdir)/src/libostree/libostree-devel.sym
#endif
# http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html
wl_versionscript_arg = -Wl,--version-script=
diff --git a/Makefile-tests.am b/Makefile-tests.am
index a4179377..3e4f17a5 100644
--- a/Makefile-tests.am
+++ b/Makefile-tests.am
@@ -114,6 +114,8 @@ _installed_or_uninstalled_test_scripts = \
tests/test-reset-nonlinear.sh \
tests/test-oldstyle-partial.sh \
tests/test-delta.sh \
+ tests/test-delta-sign.sh \
+ tests/test-delta-ed25519.sh \
tests/test-xattrs.sh \
tests/test-auto-summary.sh \
tests/test-prune.sh \
diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt
index 979c8e93..66b42233 100644
--- a/apidoc/ostree-sections.txt
+++ b/apidoc/ostree-sections.txt
@@ -412,7 +412,9 @@ ostree_repo_list_commit_objects_starting_with
ostree_repo_list_static_delta_names
OstreeStaticDeltaGenerateOpt
ostree_repo_static_delta_generate
+ostree_repo_static_delta_execute_offline_with_signature
ostree_repo_static_delta_execute_offline
+ostree_repo_static_delta_verify_signature
ostree_repo_traverse_new_reachable
ostree_repo_traverse_new_parents
ostree_repo_traverse_parents_get_commits
diff --git a/bash/ostree b/bash/ostree
index 7256e40a..d00695ef 100644
--- a/bash/ostree
+++ b/bash/ostree
@@ -1532,6 +1532,9 @@ _ostree_static_delta_apply_offline() {
"
local options_with_args="
+ --sign-type
+ --keys-file
+ --keys-dir
--repo
"
@@ -1613,6 +1616,8 @@ _ostree_static_delta_generate() {
--repo
--set-endianness
--to
+ --sign
+ --sign-type
"
local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" )
@@ -1630,6 +1635,9 @@ _ostree_static_delta_generate() {
COMPREPLY=( $( compgen -W "l B" -- "$cur" ) )
return 0
;;
+ $options_with_args_glob )
+ return 0
+ ;;
esac
case "$cur" in
@@ -1704,6 +1712,40 @@ _ostree_static_delta_show() {
return 0
}
+_ostree_static_delta_verify() {
+ local boolean_options="
+ $main_boolean_options
+ "
+
+ local options_with_args="
+ --sign-type
+ --keys-file
+ --keys-dir
+ --repo
+ "
+
+ local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" )
+
+ case "$prev" in
+ --keys-file|--keys-dir|--repo)
+ __ostree_compreply_dirs_only
+ return 0
+ ;;
+ $options_with_args_glob )
+ return 0
+ ;;
+ esac
+
+ case "$cur" in
+ -*)
+ local all_options="$boolean_options $options_with_args"
+ __ostree_compreply_all_options
+ ;;
+ esac
+
+ return 0
+}
+
_ostree_static_delta() {
local subcommands="
apply-offline
diff --git a/man/ostree-static-delta.xml b/man/ostree-static-delta.xml
index dfeef28b..440ada41 100644
--- a/man/ostree-static-delta.xml
+++ b/man/ostree-static-delta.xml
@@ -63,7 +63,10 @@ Boston, MA 02111-1307, USA.
ostree static-delta generate --to=REV OPTIONS
- ostree static-delta apply-offline PATH
+ ostree static-delta apply-offline OPTIONS PATH KEY-ID
+
+
+ ostree static-delta verify OPTIONS STATIC-DELTA KEY-ID
@@ -113,6 +116,159 @@ Boston, MA 02111-1307, USA.
+
+ =ENGINE
+
+
+ Use particular signature engine. Currently
+ available ed25519 and dummy
+ signature types.
+
+ The default is ed25519.
+
+
+
+
+ ="KEY-ID"
+
+ There KEY-ID is:
+
+
+
+
+ base64-encoded secret key for signing.
+
+
+
+
+
+
+ ASCII-string used as secret key.
+
+
+
+
+
+
+
+
+
+ 'Apply-offline' Options
+
+
+
+
+
+
+
+
+
+
+ base64-encoded public key for verifying.
+
+
+
+
+
+
+ ASCII-string used as public key.
+
+
+
+
+
+
+
+ =ENGINE
+
+
+ Use particular signature engine. Currently
+ available ed25519 and dummy
+ signature types.
+
+
+
+
+
+
+ Read key(s) from file filename.
+
+
+
+ Valid for ed25519 signature type.
+ For ed25519 this file must contain base64-encoded
+ public key(s) per line for verifying.
+
+
+
+
+
+
+ Redefine the system path, where to search files and subdirectories with
+ well-known and revoked keys.
+
+
+
+
+
+
+ 'Verify' Options
+
+
+
+
+
+
+
+
+
+
+ base64-encoded public key for verifying.
+
+
+
+
+
+
+ ASCII-string used as public key.
+
+
+
+
+
+
+
+ =ENGINE
+
+
+ Use particular signature engine. Currently
+ available ed25519 and dummy
+ signature types.
+
+ The default is ed25519.
+
+
+
+
+
+
+ Read key(s) from file filename.
+
+
+
+ Valid for ed25519 signature type.
+ For ed25519 this file must contain base64-encoded
+ public key(s) per line for verifying.
+
+
+
+
+
+
+ Redefine the system path, where to search files and subdirectories with
+ well-known and revoked keys.
+
+
diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym
index 7f1f7e3e..a15654c1 100644
--- a/src/libostree/libostree-devel.sym
+++ b/src/libostree/libostree-devel.sym
@@ -22,6 +22,8 @@ global:
/* Add symbols here, and uncomment the bits in
* Makefile-libostree.am to enable this too.
*/
+ ostree_repo_static_delta_execute_offline_with_signature;
+ ostree_repo_static_delta_verify_signature;
} LIBOSTREE_2020.4;
/* Stub section for the stable release *after* this development one; don't
diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c
index 3e9a8e30..753f8aee 100644
--- a/src/libostree/ostree-repo-static-delta-compilation.c
+++ b/src/libostree/ostree-repo-static-delta-compilation.c
@@ -36,6 +36,8 @@
#include "libglnx.h"
#include "ostree-varint.h"
#include "bsdiff/bsdiff.h"
+#include "ostree-autocleanups.h"
+#include "ostree-sign.h"
#define CONTENT_SIZE_SIMILARITY_THRESHOLD_PERCENT (30)
@@ -1335,6 +1337,8 @@ get_fallback_headers (OstreeRepo *self,
* - verbose: b: Print diagnostic messages. Default FALSE.
* - endianness: b: Deltas use host byte order by default; this option allows choosing (G_BIG_ENDIAN or G_LITTLE_ENDIAN)
* - filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository.
+ * - sign-name: ay: Signature type to use.
+ * - sign-key-ids: as: Array of keys used to sign delta superblock.
*/
gboolean
ostree_repo_static_delta_generate (OstreeRepo *self,
@@ -1368,6 +1372,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
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;
+ const char *opt_sign_name;
+ const char **opt_key_ids;
if (!g_variant_lookup (params, "min-fallback-size", "u", &min_fallback_size))
min_fallback_size = 4;
@@ -1407,6 +1413,12 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
if (!g_variant_lookup (params, "filename", "^&ay", &opt_filename))
opt_filename = NULL;
+ if (!g_variant_lookup (params, "sign-name", "^&ay", &opt_sign_name))
+ opt_sign_name = NULL;
+
+ if (!g_variant_lookup (params, "sign-key-ids", "^a&s", &opt_key_ids))
+ opt_key_ids = NULL;
+
if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, to,
&to_commit, error))
return FALSE;
@@ -1442,7 +1454,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
cancellable, error))
return FALSE;
- if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC,
+ if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_RDWR | O_CLOEXEC,
&descriptor_tmpf, error))
return FALSE;
@@ -1586,12 +1598,85 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
g_printerr ("bsdiff=%u objects\n", builder.n_bsdiff);
}
- if (fchmod (descriptor_tmpf.fd, 0644) < 0)
- return glnx_throw_errno_prefix (error, "fchmod");
+ if (opt_sign_name != NULL && opt_key_ids != NULL)
+ {
+ g_autoptr(GBytes) tmpdata = NULL;
+ g_autoptr(OstreeSign) sign = NULL;
+ const gchar *signature_key = NULL;
+ g_autoptr(GVariantBuilder) signature_builder = NULL;
+ g_auto(GLnxTmpfile) descriptor_sign_tmpf = { 0, };
+ g_autoptr(OtVariantBuilder) descriptor_sign_builder = NULL;
- if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE,
- descriptor_dfd, descriptor_name, error))
- return FALSE;
+ lseek (descriptor_tmpf.fd, 0, SEEK_SET);
+ tmpdata = glnx_fd_readall_bytes (descriptor_tmpf.fd, cancellable, error);
+ if (!tmpdata)
+ return FALSE;
+
+ sign = ostree_sign_get_by_name (opt_sign_name, error);
+ if (sign == NULL)
+ return FALSE;
+
+ signature_key = ostree_sign_metadata_key (sign);
+ const gchar *signature_format = ostree_sign_metadata_format (sign);
+
+ signature_builder = g_variant_builder_new (G_VARIANT_TYPE (signature_format));
+
+ for (const char **iter = opt_key_ids; iter && *iter; iter++)
+ {
+ const char *keyid = *iter;
+ g_autoptr(GVariant) secret_key = NULL;
+ g_autoptr(GBytes) signature_bytes = NULL;
+
+ secret_key = g_variant_new_string (keyid);
+ if (!ostree_sign_set_sk (sign, secret_key, error))
+ return FALSE;
+
+ if (!ostree_sign_data (sign, tmpdata, &signature_bytes,
+ NULL, error))
+ return FALSE;
+
+ g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes));
+ }
+
+ if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC,
+ &descriptor_sign_tmpf, error))
+ return FALSE;
+
+ descriptor_sign_builder = ot_variant_builder_new (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SIGNED_FORMAT),
+ descriptor_sign_tmpf.fd);
+
+ if (!ot_variant_builder_add (descriptor_sign_builder, error, "t",
+ GUINT64_TO_BE (OSTREE_STATIC_DELTA_SIGNED_MAGIC)))
+ return FALSE;
+ if (!ot_variant_builder_add (descriptor_sign_builder, error, "@ay", ot_gvariant_new_ay_bytes (tmpdata)))
+ return FALSE;
+ if (!ot_variant_builder_open (descriptor_sign_builder, G_VARIANT_TYPE ("a{sv}"), error))
+ return FALSE;
+ if (!ot_variant_builder_add (descriptor_sign_builder, error, "{sv}",
+ signature_key, g_variant_builder_end(signature_builder)))
+ return FALSE;
+ if (!ot_variant_builder_close (descriptor_sign_builder, error))
+ return FALSE;
+
+ if (!ot_variant_builder_end (descriptor_sign_builder, error))
+ return FALSE;
+
+ if (fchmod (descriptor_sign_tmpf.fd, 0644) < 0)
+ return glnx_throw_errno_prefix (error, "fchmod");
+
+ if (!glnx_link_tmpfile_at (&descriptor_sign_tmpf, GLNX_LINK_TMPFILE_REPLACE,
+ descriptor_dfd, descriptor_name, error))
+ return FALSE;
+ }
+ else
+ {
+ if (fchmod (descriptor_tmpf.fd, 0644) < 0)
+ return glnx_throw_errno_prefix (error, "fchmod");
+
+ if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE,
+ descriptor_dfd, descriptor_name, error))
+ return FALSE;
+ }
return TRUE;
}
diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c
index 835ec7f3..b16764ae 100644
--- a/src/libostree/ostree-repo-static-delta-core.c
+++ b/src/libostree/ostree-repo-static-delta-core.c
@@ -210,27 +210,127 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo,
return TRUE;
}
+static gboolean
+_ostree_repo_static_delta_is_signed (OstreeRepo *self,
+ int fd,
+ GPtrArray **out_value,
+ GError **error)
+{
+ g_autoptr(GVariant) delta = NULL;
+ g_autoptr(GVariant) delta_sign_magic = NULL;
+ g_autoptr(GVariant) delta_sign = NULL;
+ GVariantIter iter;
+ GVariant *item;
+ g_autoptr(GPtrArray) signatures = NULL;
+ gboolean ret = FALSE;
+
+ if (out_value)
+ *out_value = NULL;
+
+ if (!ot_variant_read_fd (fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta, error))
+ return FALSE;
+
+ delta_sign_magic = g_variant_get_child_value (delta, 0);
+ if (delta_sign_magic == NULL)
+ return glnx_throw (error, "no signatures in static-delta");
+
+ if (GUINT64_FROM_BE (g_variant_get_uint64 (delta_sign_magic)) != OSTREE_STATIC_DELTA_SIGNED_MAGIC)
+ return glnx_throw (error, "no signatures in static-delta");
+
+ delta_sign = g_variant_get_child_value (delta, 2);
+ if (delta_sign == NULL)
+ return glnx_throw (error, "no signatures in static-delta");
+
+ if (out_value)
+ signatures = g_ptr_array_new_with_free_func (g_free);
+
+ /* Check if there are signatures in the superblock */
+ g_variant_iter_init (&iter, delta_sign);
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ g_autoptr(GVariant) key_v = g_variant_get_child_value (item, 0);
+ const char *str = g_variant_get_string (key_v, NULL);
+ if (g_str_has_prefix (str, "ostree.sign."))
+ {
+ ret = TRUE;
+ if (signatures)
+ g_ptr_array_add (signatures, g_strdup (str + strlen ("ostree.sign.")));
+ }
+ g_variant_unref (item);
+ }
+
+ if (out_value && ret)
+ ot_transfer_out_value (out_value, &signatures);
+
+ return ret;
+}
+
+static gboolean
+_ostree_repo_static_delta_verify_signature (OstreeRepo *self,
+ int fd,
+ OstreeSign *sign,
+ char **out_success_message,
+ GError **error)
+{
+ g_autoptr(GVariantBuilder) desc_sign_builder = NULL;
+ g_autoptr(GVariant) delta_meta = NULL;
+ g_autoptr(GVariant) delta = NULL;
+
+ if (!ot_variant_read_fd (fd, 0,
+ (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT,
+ TRUE, &delta, error))
+ return FALSE;
+
+ /* Check if there are signatures for signature engine */
+ const gchar *signature_key = ostree_sign_metadata_key(sign);
+ GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(sign);
+ delta_meta = g_variant_get_child_value (delta, 2);
+ if (delta_meta == NULL)
+ return glnx_throw (error, "no metadata in static-delta superblock");
+ g_autoptr(GVariant) signatures = g_variant_lookup_value (delta_meta,
+ signature_key,
+ signature_format);
+ if (!signatures)
+ return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key);
+
+ /* Get static delta superblock */
+ g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1);
+ if (child == NULL)
+ return glnx_throw (error, "no metadata in static-delta superblock");
+ g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes(child);
+
+ return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error);
+}
+
/**
- * ostree_repo_static_delta_execute_offline:
+ * ostree_repo_static_delta_execute_offline_with_signature:
* @self: Repo
* @dir_or_file: Path to a directory containing static delta data, or directly to the superblock
+ * @sign: Signature engine used to check superblock
* @skip_validation: If %TRUE, assume data integrity
* @cancellable: Cancellable
* @error: Error
*
* Given a directory representing an already-downloaded static delta
- * on disk, apply it, generating a new commit. The directory must be
- * named with the form "FROM-TO", where both are checksums, and it
- * must contain a file named "superblock", along with at least one part.
+ * on disk, apply it, generating a new commit.
+ * If sign is passed, the static delta signature is verified.
+ * If sign-verify-deltas configuration option is set and static delta is signed,
+ * signature verification will be mandatory before apply the static delta.
+ * The directory must be named with the form "FROM-TO", where both are
+ * checksums, and it must contain a file named "superblock", along with at least
+ * one part.
*/
gboolean
-ostree_repo_static_delta_execute_offline (OstreeRepo *self,
- GFile *dir_or_file,
- gboolean skip_validation,
- GCancellable *cancellable,
- GError **error)
+ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self,
+ GFile *dir_or_file,
+ OstreeSign *sign,
+ gboolean skip_validation,
+ GCancellable *cancellable,
+ GError **error)
{
g_autofree char *basename = NULL;
+ g_autoptr(GVariant) delta = NULL;
+ g_autoptr(GVariant) meta = NULL;
const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file);
@@ -256,10 +356,43 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
if (meta_fd < 0)
return glnx_throw_errno_prefix (error, "openat(%s)", basename);
- g_autoptr(GVariant) meta = NULL;
- if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
- FALSE, &meta, error))
- return FALSE;
+ gboolean is_signed = _ostree_repo_static_delta_is_signed (self, meta_fd, NULL, NULL);
+ if (is_signed)
+ {
+ gboolean verify_deltas;
+ gboolean verified;
+
+ if (!ot_keyfile_get_boolean_with_default (self->config, "core", "sign-verify-deltas",
+ FALSE, &verify_deltas, error))
+ return FALSE;
+
+ if (verify_deltas && !sign)
+ return glnx_throw (error, "Key is mandatory to check delta signature");
+
+ if (sign)
+ {
+ verified = _ostree_repo_static_delta_verify_signature (self, meta_fd, sign, NULL, error);
+ if (*error)
+ return FALSE;
+ if (!verified)
+ return glnx_throw (error, "Delta signature verification failed");
+ }
+
+ if (!ot_variant_read_fd (meta_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT,
+ TRUE, &delta, error))
+ return FALSE;
+
+ g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1);
+ g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes (child);
+ meta = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT,
+ bytes, FALSE);
+ }
+ else
+ {
+ if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
+ FALSE, &meta, error))
+ return FALSE;
+ }
/* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */
@@ -408,6 +541,32 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
return TRUE;
}
+/**
+ * ostree_repo_static_delta_execute_offline:
+ * @self: Repo
+ * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock
+ * @skip_validation: If %TRUE, assume data integrity
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Given a directory representing an already-downloaded static delta
+ * on disk, apply it, generating a new commit. The directory must be
+ * named with the form "FROM-TO", where both are checksums, and it
+ * must contain a file named "superblock", along with at least one part.
+ */
+gboolean
+ostree_repo_static_delta_execute_offline (OstreeRepo *self,
+ GFile *dir_or_file,
+ gboolean skip_validation,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return ostree_repo_static_delta_execute_offline_with_signature(self, dir_or_file, NULL,
+ skip_validation,
+ cancellable,
+ error);
+}
+
gboolean
_ostree_static_delta_part_open (GInputStream *part_in,
GBytes *inline_part_bytes,
@@ -748,6 +907,8 @@ _ostree_repo_static_delta_dump (OstreeRepo *self,
GError **error)
{
glnx_autofd int superblock_fd = -1;
+ g_autoptr(GVariant) delta = NULL;
+ g_autoptr(GVariant) delta_superblock = NULL;
if (strchr (delta_id, '/'))
{
@@ -766,13 +927,28 @@ _ostree_repo_static_delta_dump (OstreeRepo *self,
return FALSE;
}
- g_autoptr(GVariant) delta_superblock = NULL;
- if (!ot_variant_read_fd (superblock_fd, 0,
- (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT,
- TRUE, &delta_superblock, error))
- return FALSE;
+ gboolean is_signed = _ostree_repo_static_delta_is_signed(self, superblock_fd, NULL, NULL);
+ if (is_signed)
+ {
+ if (!ot_variant_read_fd (superblock_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT,
+ TRUE, &delta, error))
+ return FALSE;
+
+ g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1);
+ g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes(child);
+ delta_superblock = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT,
+ bytes, FALSE);
+ }
+ else
+ {
+ if (!ot_variant_read_fd (superblock_fd, 0,
+ (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT,
+ TRUE, &delta_superblock, error))
+ return FALSE;
+ }
g_print ("Delta: %s\n", delta_id);
+ g_print ("Signed: %s\n", is_signed ? "yes" : "no");
g_autoptr(GVariant) from_commit_v = NULL;
g_variant_get_child (delta_superblock, 2, "@ay", &from_commit_v);
g_autofree char *from_commit = NULL;
@@ -895,3 +1071,52 @@ _ostree_repo_static_delta_dump (OstreeRepo *self,
return TRUE;
}
+
+/**
+ * ostree_repo_static_delta_verify_signature:
+ * @self: Repo
+ * @delta_id: delta path
+ * @sign: Signature engine used to check superblock
+ * @out_success_message: success message
+ * @error: Error
+ *
+ * Verify static delta file signature.
+ *
+ * Returns: TRUE if the signature of static delta file is valid using the
+ * signature engine provided, FALSE otherwise.
+ *
+ * Since: 2020.1
+ */
+gboolean
+ostree_repo_static_delta_verify_signature (OstreeRepo *self,
+ const char *delta_id,
+ OstreeSign *sign,
+ char **out_success_message,
+ GError **error)
+{
+ g_autoptr(GVariantBuilder) desc_sign_builder = NULL;
+ g_autoptr(GVariant) delta_meta = NULL;
+ glnx_autofd int delta_fd = -1;
+
+ if (strchr (delta_id, '/'))
+ {
+ if (!glnx_openat_rdonly (AT_FDCWD, delta_id, TRUE, &delta_fd, error))
+ return FALSE;
+ }
+ else
+ {
+ g_autofree char *from = NULL;
+ g_autofree char *to = NULL;
+ if (!_ostree_parse_delta_name (delta_id, &from, &to, error))
+ return FALSE;
+
+ g_autofree char *delta_path = _ostree_get_relative_static_delta_superblock_path (from, to);
+ if (!glnx_openat_rdonly (self->repo_dir_fd, delta_path, TRUE, &delta_fd, error))
+ return FALSE;
+ }
+
+ if (!_ostree_repo_static_delta_is_signed (self, delta_fd, NULL, error))
+ return FALSE;
+
+ return _ostree_repo_static_delta_verify_signature (self, delta_fd, sign, out_success_message, error);
+}
diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h
index ff8de9d4..5a2e6879 100644
--- a/src/libostree/ostree-repo-static-delta-private.h
+++ b/src/libostree/ostree-repo-static-delta-private.h
@@ -104,6 +104,25 @@ G_BEGIN_DECLS
*/
#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")"
+/**
+ * OSTREE_STATIC_DELTA_SIGNED_FORMAT
+ *
+ * magic: t magic number, 8 bytes for alignment
+ * superblock: ay delta supeblock variant
+ * signatures: a{sv}
+ *
+ * The signed static delta starts with the 'OSTSGNDT' magic number followed by
+ * the array of bytes containing the superblock used for the signature.
+ *
+ * Then, the signatures array contains the signatures of the superblock. A
+ * signature has the following form:
+ * type: signature key
+ * signature: variant depending on type used
+ */
+#define OSTREE_STATIC_DELTA_SIGNED_FORMAT "(taya{sv})"
+
+#define OSTREE_STATIC_DELTA_SIGNED_MAGIC 0x4F535453474E4454 /* OSTSGNDT */
+
typedef enum {
OSTREE_STATIC_DELTA_OPEN_FLAGS_NONE = 0,
OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM = (1 << 0),
diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h
index e28af29c..d52fc9c3 100644
--- a/src/libostree/ostree-repo.h
+++ b/src/libostree/ostree-repo.h
@@ -32,6 +32,7 @@
#include "ostree-repo-finder.h"
#include "ostree-sepolicy.h"
#include "ostree-gpg-verify-result.h"
+#include "ostree-sign.h"
G_BEGIN_DECLS
@@ -1067,6 +1068,14 @@ gboolean ostree_repo_static_delta_generate (OstreeRepo *self,
GCancellable *cancellable,
GError **error);
+_OSTREE_PUBLIC
+gboolean ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self,
+ GFile *dir_or_file,
+ OstreeSign *sign,
+ gboolean skip_validation,
+ GCancellable *cancellable,
+ GError **error);
+
_OSTREE_PUBLIC
gboolean ostree_repo_static_delta_execute_offline (OstreeRepo *self,
GFile *dir_or_file,
@@ -1074,6 +1083,13 @@ gboolean ostree_repo_static_delta_execute_offline (OstreeRepo
GCancellable *cancellable,
GError **error);
+_OSTREE_PUBLIC
+gboolean ostree_repo_static_delta_verify_signature (OstreeRepo *self,
+ const char *delta_id,
+ OstreeSign *sign,
+ char **out_success_message,
+ GError **error);
+
_OSTREE_PUBLIC
GHashTable *ostree_repo_traverse_new_reachable (void);
diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c
index 4f9ff2b2..3e0af5bd 100644
--- a/src/ostree/ot-builtin-static-delta.c
+++ b/src/ostree/ot-builtin-static-delta.c
@@ -40,6 +40,10 @@ static gboolean opt_swap_endianness;
static gboolean opt_inline;
static gboolean opt_disable_bsdiff;
static gboolean opt_if_not_exists;
+static char **opt_key_ids;
+static char *opt_sign_name;
+static char *opt_keysfilename;
+static char *opt_keysdir;
#define BUILTINPROTO(name) static gboolean ot_static_delta_builtin_ ## name (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
@@ -48,6 +52,7 @@ BUILTINPROTO(show);
BUILTINPROTO(delete);
BUILTINPROTO(generate);
BUILTINPROTO(apply_offline);
+BUILTINPROTO(verify);
#undef BUILTINPROTO
@@ -67,6 +72,9 @@ static OstreeCommand static_delta_subcommands[] = {
{ "apply-offline", OSTREE_BUILTIN_FLAG_NONE,
ot_static_delta_builtin_apply_offline,
"Apply static delta file" },
+ { "verify", OSTREE_BUILTIN_FLAG_NONE,
+ ot_static_delta_builtin_verify,
+ "Verify static delta signatures" },
{ NULL, 0, NULL, NULL }
};
@@ -88,10 +96,20 @@ static GOptionEntry generate_options[] = {
{ "max-bsdiff-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_bsdiff_size, "Maximum size in megabytes to consider bsdiff compression for input files", NULL},
{ "max-chunk-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_chunk_size, "Maximum size of delta chunks in megabytes", NULL},
{ "filename", 0, 0, G_OPTION_ARG_FILENAME, &opt_filename, "Write the delta content to PATH (a directory). If not specified, the OSTree repository is used", "PATH"},
+ { "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Sign the delta with", "KEY_ID"},
+ { "sign-type", 0, 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_keysfilename, "Read key(s) from file", "NAME"},
+#endif
{ NULL }
};
static GOptionEntry apply_offline_options[] = {
+ { "sign-type", 0, 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_keysfilename, "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 }
};
@@ -99,6 +117,15 @@ static GOptionEntry list_options[] = {
{ NULL }
};
+static GOptionEntry verify_options[] = {
+ { "sign-type", 0, 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_keysfilename, "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
static_delta_usage (char **argv,
gboolean is_error)
@@ -326,6 +353,60 @@ ot_static_delta_builtin_generate (int argc, char **argv, OstreeCommandInvocation
if (opt_endianness || opt_swap_endianness)
g_variant_builder_add (parambuilder, "{sv}", "endianness", g_variant_new_uint32 (endianness));
+ if (opt_key_ids || opt_keysfilename)
+ {
+ g_autoptr(GPtrArray) key_ids = g_ptr_array_new ();
+
+ for (char **iter = opt_key_ids; iter != NULL && *iter != NULL; ++iter)
+ g_ptr_array_add (key_ids, *iter);
+
+ if (opt_keysfilename)
+ {
+ g_autoptr (GFile) keyfile = NULL;
+ g_autoptr (GFileInputStream) key_stream_in = NULL;
+ g_autoptr (GDataInputStream) key_data_in = NULL;
+
+ if (!g_file_test (opt_keysfilename, G_FILE_TEST_IS_REGULAR))
+ {
+ g_warning ("Can't open file '%s' with keys", opt_keysfilename);
+ return glnx_throw (error, "File object '%s' is not a regular file", opt_keysfilename);
+ }
+
+ keyfile = g_file_new_for_path (opt_keysfilename);
+ 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);
+
+ /* 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)
+ return FALSE;
+
+ if (line == NULL)
+ break;
+
+ // Pass the key as a string
+ g_ptr_array_add (key_ids, g_strdup (line));
+ }
+ }
+
+ g_autoptr(GVariant) key_ids_v = g_variant_new_strv ((const char *const *)key_ids->pdata,
+ key_ids->len);
+ g_variant_builder_add (parambuilder, "{s@v}", "sign-key-ids",
+ g_variant_new_variant (g_steal_pointer (&key_ids_v)));
+ }
+ opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
+ g_variant_builder_add (parambuilder, "{sv}", "sign-name",
+ g_variant_new_bytestring (opt_sign_name));
+
g_print ("Generating static delta:\n");
g_print (" From: %s\n", from_resolved ? from_resolved : "empty");
g_print (" To: %s\n", to_resolved);
@@ -347,6 +428,9 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc
{
g_autoptr(GOptionContext) context = NULL;
g_autoptr(OstreeRepo) repo = NULL;
+ g_autoptr (OstreeSign) sign = NULL;
+ char **key_ids;
+ int n_key_ids;
context = g_option_context_new ("");
if (!ostree_option_context_parse (context, apply_offline_options, &argc, &argv, invocation, &repo, cancellable, error))
@@ -362,13 +446,59 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc
return FALSE;
}
+#if defined(HAVE_LIBSODIUM)
+ /* Initialize crypto system */
+ opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
+#endif
+
+ if (opt_sign_name)
+ {
+ sign = ostree_sign_get_by_name (opt_sign_name, error);
+ if (!sign)
+ return glnx_throw (error, "Signing type %s is not supported", opt_sign_name);
+
+ key_ids = argv + 3;
+ n_key_ids = argc - 3;
+ for (int i = 0; i < n_key_ids; i++)
+ {
+ g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]);
+ if (!ostree_sign_add_pk(sign, pk, error))
+ return FALSE;
+ }
+ if ((n_key_ids == 0) || opt_keysfilename)
+ {
+ g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ g_autoptr (GVariant) options = NULL;
+
+ /* 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_keysfilename)
+ g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename));
+ options = g_variant_builder_end (builder);
+
+ if (!ostree_sign_load_pk (sign, options, error))
+ {
+ /* If it fails to load system default public keys, consider there no signature engine */
+ if (!opt_keysdir && !opt_keysfilename)
+ {
+ g_clear_error(error);
+ g_clear_object(&sign);
+ }
+ else
+ return FALSE;
+ }
+ }
+ }
+
const char *patharg = argv[2];
g_autoptr(GFile) path = g_file_new_for_path (patharg);
if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
return FALSE;
- if (!ostree_repo_static_delta_execute_offline (repo, path, FALSE, cancellable, error))
+ if (!ostree_repo_static_delta_execute_offline_with_signature (repo, path, sign, FALSE, cancellable, error))
return FALSE;
if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
@@ -377,6 +507,68 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc
return TRUE;
}
+static gboolean
+ot_static_delta_builtin_verify (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
+{
+ g_autoptr (GOptionContext) context = g_option_context_new ("STATIC-DELTA-FILE [KEY-ID...]");
+ g_autoptr (OstreeRepo) repo = NULL;
+ gboolean verified;
+ char **key_ids;
+ int n_key_ids;
+
+ if (!ostree_option_context_parse (context, verify_options, &argc, &argv, invocation, &repo, cancellable, error))
+ return FALSE;
+
+ if (argc < 3)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "DELTA must be specified");
+ return FALSE;
+ }
+
+ opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519;
+
+ const char *delta_id = argv[2];
+
+ g_autoptr (OstreeSign) sign = ostree_sign_get_by_name (opt_sign_name, error);
+ if (!sign)
+ {
+ g_print("Sign-type not supported\n");
+ return FALSE;
+ }
+
+ key_ids = argv + 3;
+ n_key_ids = argc - 3;
+ for (int i = 0; i < n_key_ids; i++)
+ {
+ g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]);
+ if (!ostree_sign_add_pk(sign, pk, error))
+ return FALSE;
+ }
+ if ((n_key_ids == 0) || opt_keysfilename)
+ {
+ 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_keysfilename)
+ g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename));
+ options = g_variant_builder_end (builder);
+
+ if (!ostree_sign_load_pk (sign, options, error))
+ return FALSE;
+ }
+
+ verified = ostree_repo_static_delta_verify_signature (repo, delta_id, sign, NULL, error);
+ g_print ("Verification %s\n", verified ? "OK" : "fails");
+
+ return verified;
+}
+
gboolean
ostree_builtin_static_delta (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
{
diff --git a/tests/libtest.sh b/tests/libtest.sh
index ca457fa2..7c66a5c6 100755
--- a/tests/libtest.sh
+++ b/tests/libtest.sh
@@ -700,6 +700,12 @@ has_sign_ed25519 () {
return ${ret}
}
+skip_without_sign_ed25519() {
+ if ! has_sign_ed25519; then
+ skip "no ed25519 support compiled in"
+ fi
+}
+
# Keys for ed25519 signing tests
ED25519PUBLIC=
ED25519SEED=
diff --git a/tests/test-delta-ed25519.sh b/tests/test-delta-ed25519.sh
new file mode 100755
index 00000000..ef732cf9
--- /dev/null
+++ b/tests/test-delta-ed25519.sh
@@ -0,0 +1,322 @@
+#!/bin/bash
+#
+# Copyright (C) 2011,2013 Colin Walters
+#
+# 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.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+skip_without_user_xattrs
+
+skip_without_sign_ed25519
+
+bindatafiles="bash true ostree"
+
+echo '1..12'
+
+mkdir repo
+ostree_repo_init repo --mode=archive
+
+mkdir files
+for bin in ${bindatafiles}; do
+ cp $(which ${bin}) files
+done
+
+${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files
+
+function permuteFile() {
+ permutation=$(($1 % 2))
+ output=$2
+ case $permutation in
+ 0) dd if=/dev/zero count=40 bs=1 >> $output;;
+ 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;;
+ esac
+}
+
+function permuteDirectory() {
+ permutation=$1
+ dir=$2
+ for x in ${dir}/*; do
+ for z in $(seq ${permutation}); do
+ permuteFile ${z} ${x}
+ done
+ done
+}
+
+get_assert_one_direntry_matching() {
+ local path=$1
+ local r=$2
+ local child=""
+ local bn
+ for p in ${path}/*; do
+ bn=$(basename $p)
+ if ! echo ${bn} | grep -q "$r"; then
+ continue
+ fi
+ if test -z "${child}"; then
+ child=${bn}
+ else
+ assert_not_reached "Expected only one child matching ${r} in ${path}";
+ fi
+ done
+ if test -z "${child}"; then
+ assert_not_reached "Failed to find child matching ${r}"
+ fi
+ echo ${child}
+}
+
+origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test)
+
+permuteDirectory 1 files
+${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files
+
+newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test)
+
+# Test ostree sign with 'ed25519' module
+gen_ed25519_keys
+PUBLIC=${ED25519PUBLIC}
+SEED=${ED25519SEED}
+SECRET=${ED25519SECRET}
+WRONG_PUBLIC="$(gen_ed25519_random_public)"
+
+SECRETKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)"
+echo ${SECRET} > ${SECRETKEYS}
+
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-signed-1.txt
+assert_file_has_content show-ed25519-key-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-signed-2.txt
+assert_file_has_content show-ed25519-key-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-signed-3.txt
+assert_file_has_content show-ed25519-key-signed-3.txt "Verification OK"
+
+deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.')
+deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-')
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-inline-signed-1.txt
+assert_file_has_content show-ed25519-key-inline-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-inline-signed-2.txt
+assert_file_has_content show-ed25519-key-inline-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-inline-signed-3.txt
+assert_file_has_content show-ed25519-key-inline-signed-3.txt "Verification OK"
+
+echo 'ok verified with ed25519 (sign - key)'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-signed-1.txt
+assert_file_has_content show-ed25519-keyfile-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-signed-2.txt
+assert_file_has_content show-ed25519-keyfile-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-signed-3.txt
+assert_file_has_content show-ed25519-keyfile-signed-3.txt "Verification OK"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-inline-signed-1.txt
+assert_file_has_content show-ed25519-keyfile-inline-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-inline-signed-2.txt
+assert_file_has_content show-ed25519-keyfile-inline-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-inline-signed-3.txt
+assert_file_has_content show-ed25519-keyfile-inline-signed-3.txt "Verification OK"
+
+echo 'ok verified with ed25519 (keyfile - key)'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-signed.txt && exit 1
+assert_file_has_content show-ed25519-key-bad-signed.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-inline-signed.txt && exit 1
+assert_file_has_content show-ed25519-key-bad-inline-signed.txt "Verification fails"
+
+echo 'ok Verification fails with ed25519 (sign - bad key)'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-signed.txt && exit 1
+assert_file_has_content show-ed25519-keyfile-bad-signed.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-inline-signed.txt && exit 1
+assert_file_has_content show-ed25519-keyfile-bad-inline-signed.txt "Verification fails"
+
+echo 'ok Verification fails with ed25519 (keyfile - bad key)'
+
+# Prepare files with public ed25519 signatures
+PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)"
+for((i=0;i<100;i++)); do
+ # Generate a list with some public signatures
+ gen_ed25519_random_public
+done > ${PUBKEYS}
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-1.txt && exit 1
+assert_file_has_content show-ed25519-file-bad-signed-1.txt "Verification fails"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-2.txt && exit 1
+assert_file_has_content show-ed25519-file-bad-signed-2.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-1.txt && exit 1
+assert_file_has_content show-ed25519-file-inline-bad-signed-1.txt "Verification fails"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-2.txt && exit 1
+assert_file_has_content show-ed25519-file-inline-bad-signed-2.txt "Verification fails"
+
+echo 'ok Verification fails with ed25519 (sign - bad keys)'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-3.txt && exit 1
+assert_file_has_content show-ed25519-file-bad-signed-3.txt "Verification fails"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-4.txt && exit 1
+assert_file_has_content show-ed25519-file-bad-signed-4.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-3.txt && exit 1
+assert_file_has_content show-ed25519-file-inline-bad-signed-3.txt "Verification fails"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-4.txt && exit 1
+assert_file_has_content show-ed25519-file-inline-bad-signed-4.txt "Verification fails"
+
+echo 'ok Verification fails with ed25519 (keyfile - bad keys)'
+
+# Add correct key into the list
+echo ${PUBLIC} >> ${PUBKEYS}
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-1.txt
+assert_file_has_content show-ed25519-file-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-2.txt
+assert_file_has_content show-ed25519-file-signed-2.txt "Verification OK"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-1.txt
+assert_file_has_content show-ed25519-file-inline-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-2.txt
+assert_file_has_content show-ed25519-file-inline-signed-2.txt "Verification OK"
+
+echo 'ok verified with ed25519 (sign - file)'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-3.txt
+assert_file_has_content show-ed25519-file-signed-3.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-4.txt
+assert_file_has_content show-ed25519-file-signed-4.txt "Verification OK"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-3.txt
+assert_file_has_content show-ed25519-file-inline-signed-3.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-4.txt
+assert_file_has_content show-ed25519-file-inline-signed-4.txt "Verification OK"
+
+echo 'ok verified with ed25519 (keyfile - file)'
+
+# Test ostree sign with multiple 'ed25519' keys
+gen_ed25519_keys
+PUBLIC2=${ED25519PUBLIC}
+SEED2=${ED25519SEED}
+SECRET2=${ED25519SECRET}
+
+echo ${SECRET2} >> ${SECRETKEYS}
+echo ${PUBLIC2} >> ${PUBKEYS}
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-signed-1.txt
+assert_file_has_content show-ed25519-multiplekeys-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-signed-2.txt
+assert_file_has_content show-ed25519-multiplekeys-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-signed.txt && exit 1
+assert_file_has_content show-ed25519-multiplekeys-bad-signed.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-signed-3.txt
+assert_file_has_content show-ed25519-multiplekeys-signed-3.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-signed-4.txt
+assert_file_has_content show-ed25519-multiplekeys-signed-4.txt "Verification OK"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-inline-signed-1.txt
+assert_file_has_content show-ed25519-multiplekeys-inline-signed-1.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-inline-signed-2.txt
+assert_file_has_content show-ed25519-multiplekeys-inline-signed-2.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-inline-signed.txt && exit 1
+assert_file_has_content show-ed25519-multiplekeys-bad-inline-signed.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-inline-signed-3.txt
+assert_file_has_content show-ed25519-multiplekeys-inline-signed-3.txt "Verification OK"
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-inline-signed-4.txt
+assert_file_has_content show-ed25519-multiplekeys-inline-signed-4.txt "Verification OK"
+
+echo 'ok verified with ed25519 (multiple keys)'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=ed25519 --keys-file=${PUBKEYS} repo/deltas/${deltaprefix}/${deltadir}
+${CMD_PREFIX} ostree --repo=repo2 fsck
+${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null
+
+echo 'ok apply offline with ed25519 (keyfile)'
+
+mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir}
+${CMD_PREFIX} ostree --repo=repo2 fsck
+${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null
+
+echo 'ok apply offline with ed25519 (keydir)'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+if ${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir}; then
+ exit 1
+fi
+
+rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d
+
+echo 'ok apply offline with ed25519 revoking key mechanism (keydir)'
diff --git a/tests/test-delta-sign.sh b/tests/test-delta-sign.sh
new file mode 100755
index 00000000..86f12f96
--- /dev/null
+++ b/tests/test-delta-sign.sh
@@ -0,0 +1,174 @@
+#!/bin/bash
+#
+# Copyright (C) 2011,2013 Colin Walters
+#
+# 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.
+
+set -euo pipefail
+
+. $(dirname $0)/libtest.sh
+
+skip_without_user_xattrs
+
+bindatafiles="bash true ostree"
+
+echo '1..7'
+
+# This is explicitly opt in for testing
+export OSTREE_DUMMY_SIGN_ENABLED=1
+
+mkdir repo
+ostree_repo_init repo --mode=archive
+
+mkdir files
+for bin in ${bindatafiles}; do
+ cp $(which ${bin}) files
+done
+
+${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files
+
+function permuteFile() {
+ permutation=$(($1 % 2))
+ output=$2
+ case $permutation in
+ 0) dd if=/dev/zero count=40 bs=1 >> $output;;
+ 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;;
+ esac
+}
+
+function permuteDirectory() {
+ permutation=$1
+ dir=$2
+ for x in ${dir}/*; do
+ for z in $(seq ${permutation}); do
+ permuteFile ${z} ${x}
+ done
+ done
+}
+
+get_assert_one_direntry_matching() {
+ local path=$1
+ local r=$2
+ local child=""
+ local bn
+ for p in ${path}/*; do
+ bn=$(basename $p)
+ if ! echo ${bn} | grep -q "$r"; then
+ continue
+ fi
+ if test -z "${child}"; then
+ child=${bn}
+ else
+ assert_not_reached "Expected only one child matching ${r} in ${path}";
+ fi
+ done
+ if test -z "${child}"; then
+ assert_not_reached "Failed to find child matching ${r}"
+ fi
+ echo ${child}
+}
+
+origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test)
+
+permuteDirectory 1 files
+${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files
+
+newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test)
+
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev}
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-not-signed.txt 2>&1 && exit 1
+assert_file_has_content show-not-signed.txt "Verification fails"
+assert_file_has_content show-not-signed.txt "no signatures in static-delta"
+
+deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.')
+deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-')
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-inline-not-signed.txt 2>&1 && exit 1
+assert_file_has_content show-not-signed.txt "Verification fails"
+assert_file_has_content show-not-signed.txt "no signatures in static-delta"
+
+echo 'ok verify ok with unsigned deltas'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-signed.txt
+assert_file_has_content show-dummy-signed.txt "Verification OK"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-inline-signed.txt
+assert_file_has_content show-dummy-inline-signed.txt "Verification OK"
+
+echo 'ok verified with dummy'
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-signed.txt && exit 1
+assert_file_has_content show-dummy-bad-signed.txt "Verification fails"
+
+rm -rf repo/deltas/${deltaprefix}/${deltadir}/*
+${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign
+${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-inline-signed.txt && exit 1
+assert_file_has_content show-dummy-bad-inline-signed.txt "Verification fails"
+
+echo 'ok verification failed with dummy and bad key'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir}
+${CMD_PREFIX} ostree --repo=repo2 fsck
+${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null
+
+echo 'ok apply offline with no signature verification and no key'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+${CMD_PREFIX} ostree --repo=repo2 config set core.sign-verify-deltas true
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir} 2> apply-offline-verification-no-key.txt && exit 1
+assert_file_has_content apply-offline-verification-no-key.txt "Key is mandatory to check delta signature"
+
+echo 'ok apply offline failed with signature verification forced and no key'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} dummysign
+${CMD_PREFIX} ostree --repo=repo2 fsck
+${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null
+
+echo 'ok apply offline with dummy'
+
+rm -rf repo2
+ostree_repo_init repo2 --mode=bare-user
+
+${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev}
+${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null
+${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} badsign 2> apply-offline-bad-key.txt && exit 1
+assert_file_has_content apply-offline-bad-key.txt "signature: dummy: incorrect signature"
+
+echo 'ok apply offline failed with dummy and bad key'