mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-29 16:38:47 +00:00
Add a simple signing server that operates on .cab files
This commit is contained in:
parent
1a886b1b76
commit
691e02d652
@ -156,6 +156,11 @@ if test x$enable_uefi != xno; then
|
||||
fi
|
||||
AM_CONDITIONAL(HAVE_UEFI, test x$enable_uefi = xyes)
|
||||
|
||||
# Build the signing server
|
||||
AC_ARG_ENABLE(fwsignd, AS_HELP_STRING([--enable-fwsignd],[Enable fwsignd support]),
|
||||
enable_fwsignd=$enableval, enable_fwsignd=no)
|
||||
AM_CONDITIONAL(HAVE_FWSIGND, test x$enable_fwsignd = xyes)
|
||||
|
||||
# systemd support
|
||||
AC_ARG_WITH([systemdunitdir],
|
||||
AS_HELP_STRING([--with-systemdunitdir=DIR], [Directory for systemd service files]),
|
||||
|
@ -44,11 +44,22 @@ Requires: %{name} = %{version}-%{release}
|
||||
%description devel
|
||||
Files for development with %{name}.
|
||||
|
||||
%package sign
|
||||
Summary: Firmware signing server
|
||||
Requires: %{name} = %{version}-%{release}
|
||||
|
||||
%description sign
|
||||
This package provides a signing server suitable for automatically signing
|
||||
firmware in .cab archives.
|
||||
The values in /etc/fwsignd.conf file should be set before starting the daemon.
|
||||
You probably don't need this package unless you're implementing a LVFS clone.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
%configure \
|
||||
--enable-fwsignd \
|
||||
--disable-static \
|
||||
--disable-rpath \
|
||||
--disable-silent-rules \
|
||||
@ -86,7 +97,8 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';'
|
||||
%{_datadir}/polkit-1/rules.d/org.freedesktop.fwupd.rules
|
||||
%{_datadir}/dbus-1/system-services/org.freedesktop.fwupd.service
|
||||
%{_datadir}/man/man1/fwupdmgr.1.gz
|
||||
%{_unitdir}/*.service
|
||||
%{_unitdir}/fwupd-offline-update.service
|
||||
%{_unitdir}/fwupd.service
|
||||
%{_unitdir}/system-update.target.wants/*.service
|
||||
%dir %{_localstatedir}/lib/fwupd
|
||||
%{_libdir}/lib*.so.*
|
||||
@ -96,6 +108,11 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';'
|
||||
%dir %{_localstatedir}/cache/app-info/xmls
|
||||
/usr/lib/udev/rules.d/*.rules
|
||||
|
||||
%files sign
|
||||
%config(noreplace)%{_sysconfdir}/fwsignd.conf
|
||||
%{_unitdir}/fwsignd.service
|
||||
%{_libexecdir}/fwsignd
|
||||
|
||||
%files devel
|
||||
%{_includedir}/fwupd-1
|
||||
%{_libdir}/lib*.so
|
||||
|
@ -21,6 +21,12 @@ systemdservice_in_files = \
|
||||
fwupd-offline-update.service.in
|
||||
systemdservice_DATA = $(systemdservice_in_files:.service.in=.service)
|
||||
|
||||
if HAVE_FWSIGND
|
||||
systemdservice_in_files += fwsignd.service.in
|
||||
configdir = $(sysconfdir)
|
||||
dist_config_DATA = fwsignd.conf
|
||||
endif
|
||||
|
||||
install-data-hook:
|
||||
mkdir -p $(DESTDIR)$(systemdunitdir)/system-update.target.wants
|
||||
ln -sf ../fwupd-offline-update.service $(DESTDIR)$(systemdunitdir)/system-update.target.wants/fwupd-offline-update.service
|
||||
|
14
data/fwsignd.conf
Normal file
14
data/fwsignd.conf
Normal file
@ -0,0 +1,14 @@
|
||||
[fwupd]
|
||||
|
||||
# The path where we search for .cab files
|
||||
#
|
||||
# IMPORTANT: the files we could process are deleted from this location when
|
||||
# they have been signed successfully.
|
||||
SourceDirectory=/mnt/source
|
||||
|
||||
# The path where we write new signed .cab files
|
||||
DestinationDirectory=/mnt/destination
|
||||
|
||||
# The GPG key ID used for signing -- you probably want to create a key without
|
||||
# a password, e.g. gpg2 --gen-key && gpg2 --edit-key; passwd; quit
|
||||
KeyID=12345678
|
@ -1,4 +1,5 @@
|
||||
policy/org.freedesktop.fwupd.policy.in
|
||||
src/fu-debug.c
|
||||
src/fu-main.c
|
||||
src/fu-sign.c
|
||||
src/fu-util.c
|
||||
|
@ -138,6 +138,34 @@ fwupd_CFLAGS = \
|
||||
-DFU_OFFLINE_DESTDIR=\"\" \
|
||||
$(WARNINGFLAGS_C)
|
||||
|
||||
if HAVE_FWSIGND
|
||||
libexec_PROGRAMS += fwsignd
|
||||
|
||||
fwsignd_SOURCES = \
|
||||
fu-cleanup.h \
|
||||
fu-cab.c \
|
||||
fu-cab.h \
|
||||
fu-keyring.c \
|
||||
fu-keyring.h \
|
||||
fu-debug.c \
|
||||
fu-debug.h \
|
||||
fu-sign.c
|
||||
|
||||
fwsignd_LDADD = \
|
||||
$(FWUPD_LIBS) \
|
||||
$(APPSTREAM_GLIB_LIBS) \
|
||||
$(GPGME_LIBS) \
|
||||
$(GCAB_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
fwsignd_LDFLAGS = \
|
||||
$(PIE_LDFLAGS)
|
||||
|
||||
fwsignd_CFLAGS = \
|
||||
$(WARNINGFLAGS_C)
|
||||
|
||||
endif
|
||||
|
||||
TESTS_ENVIRONMENT = \
|
||||
libtool --mode=execute valgrind \
|
||||
--quiet \
|
||||
|
@ -95,6 +95,96 @@ fu_keyring_setup (FuKeyring *keyring, GError **error)
|
||||
/* enable armor mode */
|
||||
gpgme_set_armor (keyring->priv->ctx, TRUE);
|
||||
|
||||
/* never interactive */
|
||||
gpgme_set_pinentry_mode (keyring->priv->ctx, GPGME_PINENTRY_MODE_ERROR);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_keyring_list_private_keys:
|
||||
**/
|
||||
static void
|
||||
fu_keyring_list_private_keys (FuKeyring *keyring)
|
||||
{
|
||||
gpgme_key_t key;
|
||||
gpgme_error_t err;
|
||||
|
||||
err = gpgme_op_keylist_start (keyring->priv->ctx, NULL, 1);
|
||||
while (!err) {
|
||||
_cleanup_string_free_ GString *str = NULL;
|
||||
err = gpgme_op_keylist_next (keyring->priv->ctx, &key);
|
||||
if (err)
|
||||
break;
|
||||
str = g_string_new (key->subkeys->keyid);
|
||||
g_string_append_printf (str, "\t[secret:%i, sign:%i]",
|
||||
key->subkeys->secret,
|
||||
key->subkeys->can_sign);
|
||||
if (key->uids && key->uids->name)
|
||||
g_string_append_printf (str, " %s", key->uids->name);
|
||||
if (key->uids && key->uids->email)
|
||||
g_string_append_printf (str, " <%s>", key->uids->email);
|
||||
g_debug ("%s", str->str);
|
||||
gpgme_key_release (key);
|
||||
}
|
||||
|
||||
if (gpg_err_code (err) != GPG_ERR_EOF) {
|
||||
g_warning ("can not list keys: %s\n", gpgme_strerror (err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_keyring_set_signing_key:
|
||||
**/
|
||||
gboolean
|
||||
fu_keyring_set_signing_key (FuKeyring *keyring, const gchar *key_id, GError **error)
|
||||
{
|
||||
gint n_signers;
|
||||
gpgme_error_t rc;
|
||||
gpgme_key_t key;
|
||||
|
||||
|
||||
/* setup context */
|
||||
if (!fu_keyring_setup (keyring, error))
|
||||
return FALSE;
|
||||
|
||||
/* list possible keys */
|
||||
fu_keyring_list_private_keys (keyring);
|
||||
|
||||
/* find key */
|
||||
rc = gpgme_get_key (keyring->priv->ctx, key_id, &key, 1);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"failed to find key %s: %s",
|
||||
key_id, gpgme_strerror (rc));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* select it to be used */
|
||||
gpgme_signers_clear (keyring->priv->ctx);
|
||||
rc = gpgme_signers_add (keyring->priv->ctx, key);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"failed to add signing key %s: %s",
|
||||
key_id, gpgme_strerror (rc));
|
||||
return FALSE;
|
||||
}
|
||||
gpgme_key_unref (key);
|
||||
|
||||
/* check it's selected */
|
||||
n_signers = gpgme_signers_count (keyring->priv->ctx);
|
||||
if (n_signers != 1) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"failed to check signing key %s", key_id);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,9 @@ gboolean fu_keyring_add_public_keys (FuKeyring *keyring,
|
||||
gboolean fu_keyring_add_public_key (FuKeyring *keyring,
|
||||
const gchar *filename,
|
||||
GError **error);
|
||||
gboolean fu_keyring_set_signing_key (FuKeyring *keyring,
|
||||
const gchar *key_id,
|
||||
GError **error);
|
||||
gboolean fu_keyring_verify_file (FuKeyring *keyring,
|
||||
const gchar *filename,
|
||||
const gchar *signature,
|
||||
|
290
src/fu-sign.c
Normal file
290
src/fu-sign.c
Normal file
@ -0,0 +1,290 @@
|
||||
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||
*
|
||||
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* Licensed under the GNU General Public License Version 2
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <fwupd.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fu-cleanup.h"
|
||||
#include "fu-cab.h"
|
||||
#include "fu-debug.h"
|
||||
#include "fu-keyring.h"
|
||||
|
||||
typedef struct {
|
||||
gchar *source;
|
||||
gchar *destination;
|
||||
gchar *key_id;
|
||||
} FuSignPrivate;
|
||||
|
||||
/**
|
||||
* fu_sign_process_file:
|
||||
**/
|
||||
static gboolean
|
||||
fu_sign_process_file (FuSignPrivate *priv,
|
||||
const gchar *fn_src,
|
||||
const gchar *fn_dst,
|
||||
GError **error)
|
||||
{
|
||||
gsize fw_len = 0;
|
||||
_cleanup_bytes_unref_ GBytes *fw = NULL;
|
||||
_cleanup_bytes_unref_ GBytes *sig = NULL;
|
||||
_cleanup_free_ gchar *fw_data = NULL;
|
||||
_cleanup_free_ gchar *fn_bin_asc = NULL;
|
||||
_cleanup_object_unref_ FuCab *cab = NULL;
|
||||
_cleanup_object_unref_ FuKeyring *kr = NULL;
|
||||
_cleanup_object_unref_ GFile *src = NULL;
|
||||
_cleanup_object_unref_ GFile *dst = NULL;
|
||||
const gchar *fn_bin;
|
||||
|
||||
g_info ("processing %s", fn_src);
|
||||
|
||||
/* open the .cab file and extract firmware */
|
||||
cab = fu_cab_new ();
|
||||
src = g_file_new_for_path (fn_src);
|
||||
if (!fu_cab_load_file (cab, src, NULL, error))
|
||||
return FALSE;
|
||||
if (!fu_cab_extract (cab, FU_CAB_EXTRACT_FLAG_ALL, error))
|
||||
return FALSE;
|
||||
|
||||
/* sign the .bin */
|
||||
fn_bin = fu_cab_get_filename_firmware (cab);
|
||||
g_info ("signing %s with key %s", fn_bin, priv->key_id);
|
||||
kr = fu_keyring_new ();
|
||||
if (priv->key_id != NULL) {
|
||||
if (!fu_keyring_set_signing_key (kr, priv->key_id, error))
|
||||
return FALSE;
|
||||
}
|
||||
if (!g_file_get_contents (fn_bin, &fw_data, &fw_len, error))
|
||||
return FALSE;
|
||||
fw = g_bytes_new_static (fw_data, fw_len);
|
||||
sig = fu_keyring_sign_data (kr, fw, error);
|
||||
if (sig == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* write new detached signature */
|
||||
fn_bin_asc = g_strdup_printf ("%s.asc", fn_bin);
|
||||
g_debug ("writing to %s", fn_bin_asc);
|
||||
if (!g_file_set_contents (fn_bin_asc,
|
||||
g_bytes_get_data (sig, NULL),
|
||||
g_bytes_get_size (sig),
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
/* add to file list */
|
||||
fu_cab_add_file (cab, fn_bin_asc);
|
||||
|
||||
/* add the detached signature to the .cab file */
|
||||
g_debug ("saving %s", fn_dst);
|
||||
dst = g_file_new_for_path (fn_dst);
|
||||
if (!fu_cab_save_file (cab, dst, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
/* delete the source file */
|
||||
g_debug ("deleting %s", fn_src);
|
||||
g_unlink (fn_src);
|
||||
|
||||
/* delete the working space */
|
||||
g_unlink (fn_bin_asc);
|
||||
if (!fu_cab_delete_temp_files (cab, error))
|
||||
return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_sign_coldplug:
|
||||
**/
|
||||
static gboolean
|
||||
fu_sign_coldplug (FuSignPrivate *priv, GError **error)
|
||||
{
|
||||
_cleanup_dir_close_ GDir *dir = NULL;
|
||||
|
||||
dir = g_dir_open (priv->source, 0, error);
|
||||
if (dir == NULL)
|
||||
return FALSE;
|
||||
while (TRUE) {
|
||||
const gchar *fn;
|
||||
_cleanup_free_ gchar *fn_src = NULL;
|
||||
_cleanup_free_ gchar *fn_dst = NULL;
|
||||
fn = g_dir_read_name (dir);
|
||||
if (fn == NULL)
|
||||
break;
|
||||
fn_src = g_build_filename (priv->source, fn, NULL);
|
||||
fn_dst = g_build_filename (priv->destination, fn, NULL);
|
||||
if (!fu_sign_process_file (priv, fn_src, fn_dst, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_sign_monitor_changed_cb:
|
||||
**/
|
||||
static void
|
||||
fu_sign_monitor_changed_cb (GFileMonitor *monitor,
|
||||
GFile *file, GFile *other_file,
|
||||
GFileMonitorEvent event_type,
|
||||
FuSignPrivate *priv)
|
||||
{
|
||||
_cleanup_error_free_ GError *error = NULL;
|
||||
_cleanup_free_ gchar *basename = NULL;
|
||||
_cleanup_free_ gchar *fn_dst = NULL;
|
||||
_cleanup_free_ gchar *fn_src = NULL;
|
||||
|
||||
/* only new files */
|
||||
if (event_type != G_FILE_MONITOR_EVENT_CREATED)
|
||||
return;
|
||||
fn_src = g_file_get_path (file);
|
||||
if (fn_src == NULL)
|
||||
return;
|
||||
|
||||
/* process file */
|
||||
basename = g_file_get_basename (file);
|
||||
fn_dst = g_build_filename (priv->destination, basename, NULL);
|
||||
if (!fu_sign_process_file (priv, fn_src, fn_dst, &error)) {
|
||||
g_warning ("failed to process %s: %s", fn_src, error->message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* main:
|
||||
**/
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
FuSignPrivate *priv = NULL;
|
||||
GOptionContext *context;
|
||||
GMainLoop *loop = NULL;
|
||||
gboolean ret;
|
||||
gboolean one_shot = FALSE;
|
||||
guint retval = 1;
|
||||
_cleanup_error_free_ GError *error = NULL;
|
||||
_cleanup_free_ gchar *config_file = NULL;
|
||||
_cleanup_free_ gchar *destination = NULL;
|
||||
_cleanup_free_ gchar *key_id = NULL;
|
||||
_cleanup_free_ gchar *source = NULL;
|
||||
_cleanup_keyfile_unref_ GKeyFile *config = NULL;
|
||||
const GOptionEntry options[] = {
|
||||
{ "one-shot", '\0', 0, G_OPTION_ARG_NONE, &one_shot,
|
||||
/* TRANSLATORS: exit after we've started up, used for user profiling */
|
||||
_("Exit after signing queue"), NULL },
|
||||
{ "source", 's', 0, G_OPTION_ARG_FILENAME, &source,
|
||||
/* TRANSLATORS: input location to watch */
|
||||
_("Source path for files"), NULL },
|
||||
{ "destination", 'd', 0, G_OPTION_ARG_FILENAME, &destination,
|
||||
/* TRANSLATORS: output location to put files */
|
||||
_("Destination path for files"), NULL },
|
||||
{ "key-id", 'd', 0, G_OPTION_ARG_STRING, &key_id,
|
||||
/* TRANSLATORS: output location to put files */
|
||||
_("GPG key used to sign the firmware"), NULL },
|
||||
{ NULL}
|
||||
};
|
||||
|
||||
setlocale (LC_ALL, "");
|
||||
|
||||
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
|
||||
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
||||
textdomain (GETTEXT_PACKAGE);
|
||||
|
||||
/* TRANSLATORS: program name */
|
||||
g_set_application_name (_("fwsignd"));
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
g_option_context_add_group (context, fu_debug_get_option_group ());
|
||||
/* TRANSLATORS: program summary */
|
||||
g_option_context_set_summary (context, _("Firmware signing server"));
|
||||
ret = g_option_context_parse (context, &argc, &argv, &error);
|
||||
if (!ret) {
|
||||
g_print ("failed to parse command line arguments: %s\n",
|
||||
error->message);
|
||||
retval = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* fall back to keyfile */
|
||||
config = g_key_file_new ();
|
||||
config_file = g_build_filename (SYSCONFDIR, "fwsignd.conf", NULL);
|
||||
if (!g_key_file_load_from_file (config, config_file,
|
||||
G_KEY_FILE_NONE, &error)) {
|
||||
g_print ("failed to load config file %s: %s\n",
|
||||
config_file, error->message);
|
||||
retval = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
if (source == NULL)
|
||||
source = g_key_file_get_string (config, "fwupd", "SourceDirectory", NULL);
|
||||
if (destination == NULL)
|
||||
destination = g_key_file_get_string (config, "fwupd", "DestinationDirectory", NULL);
|
||||
if (key_id == NULL)
|
||||
key_id = g_key_file_get_string (config, "fwupd", "KeyID", NULL);
|
||||
if (source == NULL || destination == NULL) {
|
||||
g_print ("source and destination required\n");
|
||||
retval = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
priv = g_new0 (FuSignPrivate, 1);
|
||||
priv->source = g_strdup (source);
|
||||
priv->destination = g_strdup (destination);
|
||||
priv->key_id = g_strdup (key_id);
|
||||
|
||||
/* process */
|
||||
g_debug ("clearing queue");
|
||||
if (!fu_sign_coldplug (priv, &error)) {
|
||||
g_print ("failed to clear queue: %s\n", error->message);
|
||||
retval = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
if (!one_shot) {
|
||||
_cleanup_object_unref_ GFileMonitor *monitor = NULL;
|
||||
_cleanup_object_unref_ GFile *src = NULL;
|
||||
g_debug ("waiting for files to appear in %s", source);
|
||||
src = g_file_new_for_path (source);
|
||||
monitor = g_file_monitor (src, G_FILE_MONITOR_NONE, NULL, &error);
|
||||
if (monitor == NULL) {
|
||||
g_print ("failed to watch %s: %s\n", source, error->message);
|
||||
retval = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
g_signal_connect (monitor, "changed",
|
||||
G_CALLBACK (fu_sign_monitor_changed_cb), priv);
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
g_main_loop_run (loop);
|
||||
}
|
||||
|
||||
/* success */
|
||||
retval = 0;
|
||||
out:
|
||||
if (priv != NULL) {
|
||||
g_free (priv->source);
|
||||
g_free (priv->destination);
|
||||
g_free (priv->key_id);
|
||||
g_free (priv);
|
||||
}
|
||||
g_option_context_free (context);
|
||||
if (loop != NULL)
|
||||
g_main_loop_unref (loop);
|
||||
return retval;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user