From ea803f1f8078fc80e401d7c57ac4400a2fe25ec2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 29 Mar 2017 13:47:41 +0200 Subject: [PATCH] OCI: Add flatpak_oci_sign_data --- Makefile.am | 4 + common/Makefile.am.inc | 3 +- common/flatpak-oci-registry.c | 430 ++++++++++++++++++++++++++++++++++ common/flatpak-oci-registry.h | 5 + configure.ac | 10 + 5 files changed, 451 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 2576d455..d71a5370 100644 --- a/Makefile.am +++ b/Makefile.am @@ -68,6 +68,10 @@ dist_triggers_SCRIPTS = \ triggers/desktop-database.trigger \ $(NULL) +# This canonicalizes the PKG_CHECK_MODULES or AM_PATH_GPGME results +INTERNAL_GPGME_CFLAGS = $(DEP_GPGME_CFLAGS) $(GPGME_PTHREAD_CFLAGS) +INTERNAL_GPGME_LIBS = $(DEP_GPGME_LIBS) $(GPGME_PTHREAD_LIBS) + lib_LTLIBRARIES = noinst_LTLIBRARIES += libglnx.la libglnx_srcpath := $(srcdir)/libglnx diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 4d09f66b..5fb83fbd 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -65,6 +65,7 @@ libflatpak_common_la_CFLAGS = \ $(JSON_CFLAGS) \ $(XAUTH_CFLAGS) \ $(LIBSECCOMP_CFLAGS) \ + $(INTERNAL_GPGME_CFLAGS) \ -I$(srcdir)/dbus-proxy \ $(NULL) -libflatpak_common_la_LIBADD = libglnx.la $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) $(JSON_LIBS) $(XAUTH_LIBS) $(LIBSECCOMP_LIBS) +libflatpak_common_la_LIBADD = libglnx.la $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) $(JSON_LIBS) $(XAUTH_LIBS) $(LIBSECCOMP_LIBS) $(INTERNAL_GPGME_LIBS) diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c index 8c99a4eb..4ea0ed53 100644 --- a/common/flatpak-oci-registry.c +++ b/common/flatpak-oci-registry.c @@ -26,6 +26,7 @@ #include "libglnx.h" +#include #include #include "flatpak-oci-registry.h" #include "flatpak-utils.h" @@ -1300,3 +1301,432 @@ flatpak_archive_read_open_fd_with_checksum (struct archive *a, return TRUE; } + +GLNX_DEFINE_CLEANUP_FUNCTION0(gpgme_data_t, flatpak_cleanup_gpgme_data, gpgme_data_release) +#define flatpak_auto_gpgme_data __attribute__((cleanup(flatpak_cleanup_gpgme_data))) + +GLNX_DEFINE_CLEANUP_FUNCTION0(gpgme_ctx_t, flatpak_cleanup_gpgme_ctx, gpgme_release) +#define flatpak_auto_gpgme_ctx __attribute__((cleanup(flatpak_cleanup_gpgme_ctx))) + +GLNX_DEFINE_CLEANUP_FUNCTION0(gpgme_key_t, flatpak_cleanup_gpgme_key, gpgme_key_release) +#define flatpak_auto_gpgme_key __attribute__((cleanup(flatpak_cleanup_gpgme_key))) + +static void +flatpak_gpgme_error_to_gio_error (gpgme_error_t gpg_error, + GError **error) +{ + GIOErrorEnum errcode; + + /* XXX This list is incomplete. Add cases as needed. */ + + switch (gpgme_err_code (gpg_error)) + { + /* special case - shouldn't be here */ + case GPG_ERR_NO_ERROR: + g_return_if_reached (); + + /* special case - abort on out-of-memory */ + case GPG_ERR_ENOMEM: + g_error ("%s: out of memory", + gpgme_strsource (gpg_error)); + + case GPG_ERR_INV_VALUE: + errcode = G_IO_ERROR_INVALID_ARGUMENT; + break; + + default: + errcode = G_IO_ERROR_FAILED; + break; + } + + g_set_error (error, G_IO_ERROR, errcode, "%s: error code %d", + gpgme_strsource (gpg_error), gpgme_err_code (gpg_error)); +} + +/**** The functions below are based on seahorse-gpgme-data.c ****/ + +static void +set_errno_from_gio_error (GError *error) +{ + /* This is the reverse of g_io_error_from_errno() */ + + g_return_if_fail (error != NULL); + + switch (error->code) + { + case G_IO_ERROR_FAILED: + errno = EIO; + break; + case G_IO_ERROR_NOT_FOUND: + errno = ENOENT; + break; + case G_IO_ERROR_EXISTS: + errno = EEXIST; + break; + case G_IO_ERROR_IS_DIRECTORY: + errno = EISDIR; + break; + case G_IO_ERROR_NOT_DIRECTORY: + errno = ENOTDIR; + break; + case G_IO_ERROR_NOT_EMPTY: + errno = ENOTEMPTY; + break; + case G_IO_ERROR_NOT_REGULAR_FILE: + case G_IO_ERROR_NOT_SYMBOLIC_LINK: + case G_IO_ERROR_NOT_MOUNTABLE_FILE: + errno = EBADF; + break; + case G_IO_ERROR_FILENAME_TOO_LONG: + errno = ENAMETOOLONG; + break; + case G_IO_ERROR_INVALID_FILENAME: + errno = EINVAL; + break; + case G_IO_ERROR_TOO_MANY_LINKS: + errno = EMLINK; + break; + case G_IO_ERROR_NO_SPACE: + errno = ENOSPC; + break; + case G_IO_ERROR_INVALID_ARGUMENT: + errno = EINVAL; + break; + case G_IO_ERROR_PERMISSION_DENIED: + errno = EPERM; + break; + case G_IO_ERROR_NOT_SUPPORTED: + errno = ENOTSUP; + break; + case G_IO_ERROR_NOT_MOUNTED: + errno = ENOENT; + break; + case G_IO_ERROR_ALREADY_MOUNTED: + errno = EALREADY; + break; + case G_IO_ERROR_CLOSED: + errno = EBADF; + break; + case G_IO_ERROR_CANCELLED: + errno = EINTR; + break; + case G_IO_ERROR_PENDING: + errno = EALREADY; + break; + case G_IO_ERROR_READ_ONLY: + errno = EACCES; + break; + case G_IO_ERROR_CANT_CREATE_BACKUP: + errno = EIO; + break; + case G_IO_ERROR_WRONG_ETAG: + errno = EACCES; + break; + case G_IO_ERROR_TIMED_OUT: + errno = EIO; + break; + case G_IO_ERROR_WOULD_RECURSE: + errno = ELOOP; + break; + case G_IO_ERROR_BUSY: + errno = EBUSY; + break; + case G_IO_ERROR_WOULD_BLOCK: + errno = EWOULDBLOCK; + break; + case G_IO_ERROR_HOST_NOT_FOUND: + errno = EHOSTDOWN; + break; + case G_IO_ERROR_WOULD_MERGE: + errno = EIO; + break; + case G_IO_ERROR_FAILED_HANDLED: + errno = 0; + break; + default: + errno = EIO; + break; + } +} + +static ssize_t +data_read_cb (void *handle, void *buffer, size_t size) +{ + GInputStream *input_stream = handle; + gsize bytes_read; + GError *local_error = NULL; + + g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), -1); + + g_input_stream_read_all (input_stream, buffer, size, + &bytes_read, NULL, &local_error); + + if (local_error != NULL) + { + set_errno_from_gio_error (local_error); + g_clear_error (&local_error); + bytes_read = -1; + } + + return bytes_read; +} + +static ssize_t +data_write_cb (void *handle, const void *buffer, size_t size) +{ + GOutputStream *output_stream = handle; + gsize bytes_written; + GError *local_error = NULL; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), -1); + + if (g_output_stream_write_all (output_stream, buffer, size, + &bytes_written, NULL, &local_error)) + { + g_output_stream_flush (output_stream, NULL, &local_error); + } + + if (local_error != NULL) + { + set_errno_from_gio_error (local_error); + g_clear_error (&local_error); + bytes_written = -1; + } + + return bytes_written; +} + +static off_t +data_seek_cb (void *handle, off_t offset, int whence) +{ + GObject *stream = handle; + GSeekable *seekable; + GSeekType seek_type = 0; + off_t position = -1; + GError *local_error = NULL; + + g_return_val_if_fail (G_IS_INPUT_STREAM (stream) || + G_IS_OUTPUT_STREAM (stream), -1); + + if (!G_IS_SEEKABLE (stream)) { + errno = EOPNOTSUPP; + goto out; + } + + switch (whence) + { + case SEEK_SET: + seek_type = G_SEEK_SET; + break; + case SEEK_CUR: + seek_type = G_SEEK_CUR; + break; + case SEEK_END: + seek_type = G_SEEK_END; + break; + default: + g_assert_not_reached (); + } + + seekable = G_SEEKABLE (stream); + + if (!g_seekable_seek (seekable, offset, seek_type, NULL, &local_error)) + { + set_errno_from_gio_error (local_error); + g_clear_error (&local_error); + goto out; + } + + position = g_seekable_tell (seekable); + +out: + return position; +} + +static void +data_release_cb (void *handle) +{ + GObject *stream = handle; + + g_return_if_fail (G_IS_INPUT_STREAM (stream) || + G_IS_OUTPUT_STREAM (stream)); + + g_object_unref (stream); +} + +static struct gpgme_data_cbs data_input_cbs = { + data_read_cb, + NULL, + data_seek_cb, + data_release_cb +}; + +static struct gpgme_data_cbs data_output_cbs = { + NULL, + data_write_cb, + data_seek_cb, + data_release_cb +}; + +static gpgme_data_t +flatpak_gpgme_data_input (GInputStream *input_stream) +{ + gpgme_data_t data = NULL; + gpgme_error_t gpg_error; + + g_return_val_if_fail (G_IS_INPUT_STREAM (input_stream), NULL); + + gpg_error = gpgme_data_new_from_cbs (&data, &data_input_cbs, input_stream); + + /* The only possible error is ENOMEM, which we abort on. */ + if (gpg_error != GPG_ERR_NO_ERROR) + { + g_assert (gpgme_err_code (gpg_error) == GPG_ERR_ENOMEM); + flatpak_gpgme_error_to_gio_error (gpg_error, NULL); + g_assert_not_reached (); + } + + g_object_ref (input_stream); + + return data; +} + +static gpgme_data_t +flatpak_gpgme_data_output (GOutputStream *output_stream) +{ + gpgme_data_t data = NULL; + gpgme_error_t gpg_error; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (output_stream), NULL); + + gpg_error = gpgme_data_new_from_cbs (&data, &data_output_cbs, output_stream); + + /* The only possible error is ENOMEM, which we abort on. */ + if (gpg_error != GPG_ERR_NO_ERROR) + { + g_assert (gpgme_err_code (gpg_error) == GPG_ERR_ENOMEM); + flatpak_gpgme_error_to_gio_error (gpg_error, NULL); + g_assert_not_reached (); + } + + g_object_ref (output_stream); + + return data; +} + +static gpgme_ctx_t +flatpak_gpgme_new_ctx (const char *homedir, + GError **error) +{ + gpgme_error_t err; + flatpak_auto_gpgme_ctx gpgme_ctx_t context = NULL; + + if ((err = gpgme_new (&context)) != GPG_ERR_NO_ERROR) + { + flatpak_gpgme_error_to_gio_error (err, error); + g_prefix_error (error, "Unable to create gpg context: "); + return NULL; + } + + if (homedir != NULL) + { + gpgme_engine_info_t info; + + info = gpgme_ctx_get_engine_info (context); + + if ((err = gpgme_ctx_set_engine_info (context, info->protocol, NULL, homedir)) + != GPG_ERR_NO_ERROR) + { + flatpak_gpgme_error_to_gio_error (err, error); + g_prefix_error (error, "Unable to set gpg homedir to '%s': ", + homedir); + return NULL; + } + } + + return g_steal_pointer (&context); +} + +GBytes * +flatpak_oci_sign_data (GBytes *data, + const gchar **key_ids, + const char *homedir, + GError **error) +{ + glnx_fd_close int tmp_fd = -1; + g_autofree char *tmp_path = NULL; + g_autoptr(GOutputStream) tmp_signature_output = NULL; + flatpak_auto_gpgme_ctx gpgme_ctx_t context = NULL; + g_autoptr(GBytes) ret_signature = NULL; + gpgme_error_t err; + flatpak_auto_gpgme_data gpgme_data_t commit_buffer = NULL; + flatpak_auto_gpgme_data gpgme_data_t signature_buffer = NULL; + g_autoptr(GMappedFile) signature_file = NULL; + int i; + + if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, "/tmp", O_RDWR | O_CLOEXEC, + &tmp_fd, &tmp_path, error)) + return NULL; + + tmp_signature_output = g_unix_output_stream_new (tmp_fd, FALSE); + + context = flatpak_gpgme_new_ctx (homedir, error); + if (!context) + return NULL; + + for (i = 0; key_ids[i] != NULL; i++) + { + flatpak_auto_gpgme_key gpgme_key_t key = NULL; + + /* Get the secret keys with the given key id */ + err = gpgme_get_key (context, key_ids[i], &key, 1); + if (gpgme_err_code (err) == GPG_ERR_EOF) + { + flatpak_fail (error,"No gpg key found with ID %s (homedir: %s)", key_ids[i], + homedir ? homedir : ""); + return NULL; + } + else if (err != GPG_ERR_NO_ERROR) + { + flatpak_fail (error, "Unable to lookup key ID %s: %d)", key_ids[i], err); + return NULL; + } + + /* Add the key to the context as a signer */ + if ((err = gpgme_signers_add (context, key)) != GPG_ERR_NO_ERROR) + { + flatpak_fail (error, "Error signing commit: %d", err); + return NULL; + } + } + + { + gsize len; + const char *buf = g_bytes_get_data (data, &len); + if ((err = gpgme_data_new_from_mem (&commit_buffer, buf, len, FALSE)) != GPG_ERR_NO_ERROR) + { + flatpak_gpgme_error_to_gio_error (err, error); + g_prefix_error (error, "Failed to create buffer from commit file: "); + return NULL; + } + } + + signature_buffer = flatpak_gpgme_data_output (tmp_signature_output); + + if ((err = gpgme_op_sign (context, commit_buffer, signature_buffer, GPGME_SIG_MODE_NORMAL)) + != GPG_ERR_NO_ERROR) + { + flatpak_gpgme_error_to_gio_error (err, error); + g_prefix_error (error, "Failure signing commit file: "); + return NULL; + } + + if (!g_output_stream_close (tmp_signature_output, NULL, error)) + return NULL; + + signature_file = g_mapped_file_new_from_fd (tmp_fd, FALSE, error); + if (!signature_file) + return NULL; + + return g_mapped_file_get_bytes (signature_file); +} diff --git a/common/flatpak-oci-registry.h b/common/flatpak-oci-registry.h index 529cef5c..a0b0c12a 100644 --- a/common/flatpak-oci-registry.h +++ b/common/flatpak-oci-registry.h @@ -116,4 +116,9 @@ gboolean flatpak_archive_read_open_fd_with_checksum (struct archive *a, GChecksum *checksum, GError **error); +GBytes *flatpak_oci_sign_data (GBytes *data, + const gchar **key_ids, + const char *homedir, + GError **error); + #endif /* __FLATPAK_OCI_REGISTRY_H__ */ diff --git a/configure.ac b/configure.ac index d31bfc1f..74a4293c 100644 --- a/configure.ac +++ b/configure.ac @@ -168,6 +168,16 @@ LIBS=$BASE_LIBS AC_CHECK_FUNCS(archive_read_support_filter_all) LIBS=$save_LIBS +LIBGPGME_DEPENDENCY="1.1.8" +PKG_CHECK_MODULES(DEP_GPGME, gpgme-pthread >= $LIBGPGME_DEPENDENCY, have_gpgme=yes, [ + m4_ifdef([AM_PATH_GPGME_PTHREAD], [ + AM_PATH_GPGME_PTHREAD($LIBGPGME_DEPENDENCY, have_gpgme=yes, have_gpgme=no) + ],[ have_gpgme=no ]) +]) +AS_IF([ test x$have_gpgme = xno ], [ + AC_MSG_ERROR([Need GPGME_PTHREAD version $LIBGPGME_DEPENDENCY or later]) +]) + AC_ARG_ENABLE([system-helper], AC_HELP_STRING([--disable-system-helper], [Disable system helper]),