forked from Mirrors/flatpak-builder
OCI: Add flatpak_oci_sign_data
parent
b5c6140c58
commit
ea803f1f80
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "libglnx.h"
|
||||
|
||||
#include <gpgme.h>
|
||||
#include <libsoup/soup.h>
|
||||
#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 : "<default>");
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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__ */
|
||||
|
|
10
configure.ac
10
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]),
|
||||
|
|
Loading…
Reference in New Issue