diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index 496baa937..a87f02504 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -73,6 +73,12 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';' %find_lang %{name} +%pre +getent group fwsignd >/dev/null || groupadd -r fwsignd +getent passwd fwsignd >/dev/null || \ + useradd -r -g fwsignd -d /var/lib/fwsignd -s /sbin/nologin \ + -c "User for fwsignd" fwsignd + %post /sbin/ldconfig %systemd_post fwupd.service @@ -112,6 +118,7 @@ find %{buildroot} -name '*.la' -exec rm -f {} ';' %config(noreplace)%{_sysconfdir}/fwsignd.conf %{_unitdir}/fwsignd.service %{_libexecdir}/fwsignd +%attr(755,fwsignd,fwsignd) %dir %{_localstatedir}/lib/fwsignd %files devel %{_includedir}/fwupd-1 diff --git a/data/fwsignd.conf b/data/fwsignd.conf index 4ef91e2fe..0c1b4e95a 100644 --- a/data/fwsignd.conf +++ b/data/fwsignd.conf @@ -4,11 +4,36 @@ # # IMPORTANT: the files we could process are deleted from this location when # they have been signed successfully. -SourceDirectory=/mnt/source +# +# You will need to modify the permissions on this directory so that the signd +# user can read from this location, e.g. +# # chown fwsignd:fwsignd /srv/fwsignd/source +# +# If you are running as non-root, you probably also want to do something like: +# # chmod g+s /srv/fwsignd/source +SourceDirectory=/mnt/fwsignd/source # The path where we write new signed .cab files -DestinationDirectory=/mnt/destination +# +# You will need to modify the permissions on this directory so that the signd +# user can write to this location, e.g. +# # chown fwsignd:fwsignd /srv/fwsignd/destination -R +DestinationDirectory=/mnt/fwsignd/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 +# a password, e.g. +# # useradd temp +# # su -l temp +# $ gpg2 --full-gen-key +# $ gpg2 --list-secret-keys +# $ gpg2 --edit-key 12345678 +# gpg> passwd +# gpg> quit +# # cp /home/temp/.gnupg /var/lib/fwsignd/ -Rv +# # chown fwsignd:fwsignd /var/lib/fwsignd/.gnupg -R KeyID=12345678 + +# Set the destination files to the same user and group as the source files +# +# This requires that the fwsignd binary is being run as the root user. +SetDestinationOwner=true diff --git a/src/Makefile.am b/src/Makefile.am index 0b1efca90..5ae5a6148 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -210,6 +210,8 @@ install-data-hook: if test -w $(DESTDIR)$(prefix)/; then \ mkdir -p $(DESTDIR)$(localstatedir)/lib/fwupd; \ chmod 0755 $(DESTDIR)$(localstatedir)/lib/fwupd; \ + mkdir -p $(DESTDIR)$(localstatedir)/lib/fwsignd; \ + chmod 0755 $(DESTDIR)$(localstatedir)/lib/fwsignd; \ mkdir -p $(DESTDIR)$(localstatedir)/cache/app-info/xmls; \ mkdir -p $(DESTDIR)$(localstatedir)/cache/app-info/icons; \ fi diff --git a/src/fu-sign.c b/src/fu-sign.c index 5f31dbeca..30fa4add8 100644 --- a/src/fu-sign.c +++ b/src/fu-sign.c @@ -21,11 +21,13 @@ #include "config.h" +#include #include #include #include #include #include +#include #include "fu-cleanup.h" #include "fu-cab.h" @@ -36,16 +38,18 @@ typedef struct { gchar *source; gchar *destination; gchar *key_id; + gboolean set_owner; + FuKeyring *keyring; } FuSignPrivate; /** - * fu_sign_process_file: + * fu_sign_process_file_cab: **/ static gboolean -fu_sign_process_file (FuSignPrivate *priv, - const gchar *fn_src, - const gchar *fn_dst, - GError **error) +fu_sign_process_file_cab (FuSignPrivate *priv, + const gchar *fn_src, + const gchar *fn_dst, + GError **error) { gsize fw_len = 0; _cleanup_bytes_unref_ GBytes *fw = NULL; @@ -53,7 +57,6 @@ fu_sign_process_file (FuSignPrivate *priv, _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; @@ -71,15 +74,10 @@ fu_sign_process_file (FuSignPrivate *priv, /* 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); + sig = fu_keyring_sign_data (priv->keyring, fw, error); if (sig == NULL) return FALSE; @@ -101,10 +99,6 @@ fu_sign_process_file (FuSignPrivate *priv, 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)) @@ -112,6 +106,105 @@ fu_sign_process_file (FuSignPrivate *priv, return TRUE; } +/** + * fu_sign_process_file_xml: + **/ +static gboolean +fu_sign_process_file_xml (FuSignPrivate *priv, + const gchar *fn_src, + const gchar *fn_dst, + GError **error) +{ + gsize xml_len = 0; + _cleanup_bytes_unref_ GBytes *xml = NULL; + _cleanup_bytes_unref_ GBytes *sig = NULL; + _cleanup_free_ gchar *xml_data = NULL; + + /* sign the firmware */ + g_info ("signing %s with key %s", fn_src, priv->key_id); + if (!g_file_get_contents (fn_src, &xml_data, &xml_len, error)) + return FALSE; + xml = g_bytes_new_static (xml_data, xml_len); + sig = fu_keyring_sign_data (priv->keyring, xml, error); + if (sig == NULL) + return FALSE; + + /* write new detached signature */ + g_debug ("writing to %s", fn_dst); + if (!g_file_set_contents (fn_dst, + g_bytes_get_data (sig, NULL), + g_bytes_get_size (sig), + error)) + return FALSE; + return TRUE; +} + +/** + * fu_sign_process_file: + **/ +static gboolean +fu_sign_process_file (FuSignPrivate *priv, + const gchar *fn_src, + GError **error) +{ + guint32 uid; + guint32 gid; + gboolean ret = FALSE; + _cleanup_free_ gchar *fn_dst = NULL; + _cleanup_free_ gchar *basename = NULL; + _cleanup_object_unref_ GFileInfo *info = NULL; + _cleanup_object_unref_ GFile *src = NULL; + + /* get the file owner */ + src = g_file_new_for_path (fn_src); + info = g_file_query_info (src, + G_FILE_ATTRIBUTE_UNIX_UID "," + G_FILE_ATTRIBUTE_UNIX_GID "," + G_FILE_ATTRIBUTE_OWNER_USER "," + G_FILE_ATTRIBUTE_OWNER_GROUP, + G_FILE_QUERY_INFO_NONE, + NULL, error); + if (info == NULL) + return FALSE; + + /* process these in different ways */ + if (g_str_has_suffix (fn_src, ".cab")) { + /* cab file */ + basename = g_path_get_basename (fn_src); + fn_dst = g_build_filename (priv->destination, basename, NULL); + ret = fu_sign_process_file_cab (priv, fn_src, fn_dst, error); + } else if (as_app_guess_source_kind (fn_src) == AS_APP_SOURCE_KIND_APPSTREAM) { + /* AppStream metadata */ + basename = g_path_get_basename (fn_src); + fn_dst = g_strdup_printf ("%s/%s.asc", priv->destination, basename); + ret = fu_sign_process_file_xml (priv, fn_src, fn_dst, error); + } else { + /* unknown */ + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "unknown file type"); + } + if (!ret) + return FALSE; + + /* set the owner:group on the new file */ + if (priv->set_owner) { + uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID); + gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID); + g_debug ("Attempting to set unix owner to %s:%s", + g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER), + g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP)); + chown (fn_dst, uid, gid); + } + + /* only delete the source file if *everything* worked */ + g_debug ("deleting %s", fn_src); + g_unlink (fn_src); + + return TRUE; +} + /** * fu_sign_coldplug: **/ @@ -126,13 +219,11 @@ fu_sign_coldplug (FuSignPrivate *priv, GError **error) 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)) + if (!fu_sign_process_file (priv, fn_src, error)) return FALSE; } return TRUE; @@ -148,8 +239,6 @@ fu_sign_monitor_changed_cb (GFileMonitor *monitor, 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 */ @@ -160,9 +249,7 @@ fu_sign_monitor_changed_cb (GFileMonitor *monitor, 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)) { + if (!fu_sign_process_file (priv, fn_src, &error)) { g_warning ("failed to process %s: %s", fn_src, error->message); return; } @@ -226,6 +313,7 @@ main (int argc, char *argv[]) /* fall back to keyfile */ config = g_key_file_new (); config_file = g_build_filename (SYSCONFDIR, "fwsignd.conf", NULL); + g_debug ("Loading fallback values from %s", config_file); 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", @@ -248,6 +336,14 @@ main (int argc, char *argv[]) priv->source = g_strdup (source); priv->destination = g_strdup (destination); priv->key_id = g_strdup (key_id); + priv->keyring = fu_keyring_new (); + priv->set_owner = g_key_file_get_boolean (config, "fwupd", "SetDestinationOwner", NULL); + if (priv->key_id != NULL && + !fu_keyring_set_signing_key (priv->keyring, priv->key_id, &error)) { + g_print ("valid GPG key required: %s\n", error->message); + retval = EXIT_FAILURE; + goto out; + } /* process */ g_debug ("clearing queue"); @@ -277,6 +373,7 @@ main (int argc, char *argv[]) retval = 0; out: if (priv != NULL) { + g_object_unref (priv->keyring); g_free (priv->source); g_free (priv->destination); g_free (priv->key_id);