Add a simple signing server that operates on .cab files

This commit is contained in:
Richard Hughes 2015-07-15 17:31:16 +01:00
parent 1a886b1b76
commit 691e02d652
9 changed files with 455 additions and 1 deletions

View File

@ -156,6 +156,11 @@ if test x$enable_uefi != xno; then
fi fi
AM_CONDITIONAL(HAVE_UEFI, test x$enable_uefi = xyes) 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 # systemd support
AC_ARG_WITH([systemdunitdir], AC_ARG_WITH([systemdunitdir],
AS_HELP_STRING([--with-systemdunitdir=DIR], [Directory for systemd service files]), AS_HELP_STRING([--with-systemdunitdir=DIR], [Directory for systemd service files]),

View File

@ -44,11 +44,22 @@ Requires: %{name} = %{version}-%{release}
%description devel %description devel
Files for development with %{name}. 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 %prep
%setup -q %setup -q
%build %build
%configure \ %configure \
--enable-fwsignd \
--disable-static \ --disable-static \
--disable-rpath \ --disable-rpath \
--disable-silent-rules \ --disable-silent-rules \
@ -86,7 +97,8 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';'
%{_datadir}/polkit-1/rules.d/org.freedesktop.fwupd.rules %{_datadir}/polkit-1/rules.d/org.freedesktop.fwupd.rules
%{_datadir}/dbus-1/system-services/org.freedesktop.fwupd.service %{_datadir}/dbus-1/system-services/org.freedesktop.fwupd.service
%{_datadir}/man/man1/fwupdmgr.1.gz %{_datadir}/man/man1/fwupdmgr.1.gz
%{_unitdir}/*.service %{_unitdir}/fwupd-offline-update.service
%{_unitdir}/fwupd.service
%{_unitdir}/system-update.target.wants/*.service %{_unitdir}/system-update.target.wants/*.service
%dir %{_localstatedir}/lib/fwupd %dir %{_localstatedir}/lib/fwupd
%{_libdir}/lib*.so.* %{_libdir}/lib*.so.*
@ -96,6 +108,11 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';'
%dir %{_localstatedir}/cache/app-info/xmls %dir %{_localstatedir}/cache/app-info/xmls
/usr/lib/udev/rules.d/*.rules /usr/lib/udev/rules.d/*.rules
%files sign
%config(noreplace)%{_sysconfdir}/fwsignd.conf
%{_unitdir}/fwsignd.service
%{_libexecdir}/fwsignd
%files devel %files devel
%{_includedir}/fwupd-1 %{_includedir}/fwupd-1
%{_libdir}/lib*.so %{_libdir}/lib*.so

View File

@ -21,6 +21,12 @@ systemdservice_in_files = \
fwupd-offline-update.service.in fwupd-offline-update.service.in
systemdservice_DATA = $(systemdservice_in_files:.service.in=.service) 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: install-data-hook:
mkdir -p $(DESTDIR)$(systemdunitdir)/system-update.target.wants mkdir -p $(DESTDIR)$(systemdunitdir)/system-update.target.wants
ln -sf ../fwupd-offline-update.service $(DESTDIR)$(systemdunitdir)/system-update.target.wants/fwupd-offline-update.service ln -sf ../fwupd-offline-update.service $(DESTDIR)$(systemdunitdir)/system-update.target.wants/fwupd-offline-update.service

14
data/fwsignd.conf Normal file
View 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

View File

@ -1,4 +1,5 @@
policy/org.freedesktop.fwupd.policy.in policy/org.freedesktop.fwupd.policy.in
src/fu-debug.c src/fu-debug.c
src/fu-main.c src/fu-main.c
src/fu-sign.c
src/fu-util.c src/fu-util.c

View File

@ -138,6 +138,34 @@ fwupd_CFLAGS = \
-DFU_OFFLINE_DESTDIR=\"\" \ -DFU_OFFLINE_DESTDIR=\"\" \
$(WARNINGFLAGS_C) $(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 = \ TESTS_ENVIRONMENT = \
libtool --mode=execute valgrind \ libtool --mode=execute valgrind \
--quiet \ --quiet \

View File

@ -95,6 +95,96 @@ fu_keyring_setup (FuKeyring *keyring, GError **error)
/* enable armor mode */ /* enable armor mode */
gpgme_set_armor (keyring->priv->ctx, TRUE); 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; return TRUE;
} }

View File

@ -59,6 +59,9 @@ gboolean fu_keyring_add_public_keys (FuKeyring *keyring,
gboolean fu_keyring_add_public_key (FuKeyring *keyring, gboolean fu_keyring_add_public_key (FuKeyring *keyring,
const gchar *filename, const gchar *filename,
GError **error); GError **error);
gboolean fu_keyring_set_signing_key (FuKeyring *keyring,
const gchar *key_id,
GError **error);
gboolean fu_keyring_verify_file (FuKeyring *keyring, gboolean fu_keyring_verify_file (FuKeyring *keyring,
const gchar *filename, const gchar *filename,
const gchar *signature, const gchar *signature,

290
src/fu-sign.c Normal file
View 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;
}