mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-19 17:55:55 +00:00
Add a 'verify' command that verifies the cryptographic hash of device firmware
Goes some way towards fixing https://github.com/hughsie/fwupd/issues/15
This commit is contained in:
parent
a1e5d86d20
commit
a043c2e376
@ -54,6 +54,7 @@ G_BEGIN_DECLS
|
||||
#define FU_DEVICE_KEY_PENDING_STATE "PendingState" /* s */
|
||||
#define FU_DEVICE_KEY_PENDING_ERROR "PendingError" /* s */
|
||||
#define FU_DEVICE_KEY_TRUSTED "Trusted" /* t */
|
||||
#define FU_DEVICE_KEY_FIRMWARE_HASH "FirmwareHash" /* s */
|
||||
|
||||
typedef struct _FuDevicePrivate FuDevicePrivate;
|
||||
typedef struct _FuDevice FuDevice;
|
||||
|
@ -782,6 +782,36 @@ fu_main_daemon_method_call (GDBusConnection *connection, const gchar *sender,
|
||||
return;
|
||||
}
|
||||
|
||||
/* return 's' */
|
||||
if (g_strcmp0 (method_name, "Verify") == 0) {
|
||||
FuDeviceItem *item = NULL;
|
||||
const gchar *id = NULL;
|
||||
_cleanup_error_free_ GError *error = NULL;
|
||||
const gchar *hash = NULL;
|
||||
|
||||
/* check the id exists */
|
||||
g_variant_get (parameters, "(&s)", &id);
|
||||
g_debug ("Called %s(%s)", method_name, id);
|
||||
item = fu_main_get_item_by_id (priv, id);
|
||||
if (item == NULL) {
|
||||
g_dbus_method_invocation_return_error (invocation,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_FOUND,
|
||||
"no such device %s",
|
||||
id);
|
||||
return;
|
||||
}
|
||||
if (!fu_provider_verify (item->provider, item->device,
|
||||
FU_PROVIDER_VERIFY_FLAG_NONE, &error)) {
|
||||
g_dbus_method_invocation_return_gerror (invocation, error);
|
||||
return;
|
||||
}
|
||||
hash = fu_device_get_metadata (item->device, FU_DEVICE_KEY_FIRMWARE_HASH);
|
||||
g_dbus_method_invocation_return_value (invocation,
|
||||
g_variant_new ("(s)", hash));
|
||||
return;
|
||||
}
|
||||
|
||||
/* return '' */
|
||||
if (g_strcmp0 (method_name, "Update") == 0) {
|
||||
FuDeviceItem *item = NULL;
|
||||
|
@ -222,6 +222,77 @@ out:
|
||||
g_debug ("Failed to close: %s", error->message);
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_chug_verify:
|
||||
**/
|
||||
static gboolean
|
||||
fu_provider_chug_verify (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
FuProviderVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuProviderChug *provider_chug = FU_PROVIDER_CHUG (provider);
|
||||
FuProviderChugPrivate *priv = provider_chug->priv;
|
||||
FuProviderChugItem *item;
|
||||
gsize len;
|
||||
_cleanup_error_free_ GError *error_local = NULL;
|
||||
_cleanup_free_ gchar *hash = NULL;
|
||||
_cleanup_free_ guint8 *data = NULL;
|
||||
|
||||
/* find item */
|
||||
item = g_hash_table_lookup (priv->devices, fu_device_get_id (device));
|
||||
if (item == NULL) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_FOUND,
|
||||
"cannot find: %s",
|
||||
fu_device_get_id (device));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if !CD_CHECK_VERSION(1,2,12)
|
||||
/* recompile colord */
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_NOT_SUPPORTED,
|
||||
"Cannot read firmware: colord too old");
|
||||
return FALSE;
|
||||
#endif
|
||||
|
||||
/* open */
|
||||
if (!fu_provider_chug_open (item, error))
|
||||
return FALSE;
|
||||
|
||||
/* get the firmware from the device */
|
||||
g_debug ("ColorHug: Verifying firmware");
|
||||
#if CD_CHECK_VERSION(1,2,12)
|
||||
ch_device_queue_read_firmware (priv->device_queue, item->usb_device,
|
||||
&data, &len);
|
||||
#endif
|
||||
fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY);
|
||||
if (!ch_device_queue_process (priv->device_queue,
|
||||
CH_DEVICE_QUEUE_PROCESS_FLAGS_NONE,
|
||||
NULL, &error_local)) {
|
||||
g_set_error (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_WRITE,
|
||||
"failed to dump firmware: %s",
|
||||
error_local->message);
|
||||
g_usb_device_close (item->usb_device, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* get the SHA1 hash */
|
||||
hash = g_compute_checksum_for_data (G_CHECKSUM_SHA1, (guchar *) data, len);
|
||||
fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH, hash);
|
||||
|
||||
/* we're done here */
|
||||
if (!g_usb_device_close (item->usb_device, &error_local))
|
||||
g_debug ("Failed to close: %s", error_local->message);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_chug_update:
|
||||
**/
|
||||
@ -593,6 +664,7 @@ fu_provider_chug_class_init (FuProviderChugClass *klass)
|
||||
provider_class->get_name = fu_provider_chug_get_name;
|
||||
provider_class->coldplug = fu_provider_chug_coldplug;
|
||||
provider_class->update_online = fu_provider_chug_update;
|
||||
provider_class->verify = fu_provider_chug_verify;
|
||||
object_class->finalize = fu_provider_chug_finalize;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (FuProviderChugPrivate));
|
||||
|
@ -174,6 +174,52 @@ fu_provider_udev_get_rom_version (const gchar *rom_fn, GError **error)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_udev_verify:
|
||||
**/
|
||||
static gboolean
|
||||
fu_provider_udev_verify (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
FuProviderVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *rom_fn;
|
||||
guint8 buffer[4096];
|
||||
_cleanup_checksum_free_ GChecksum *hash = NULL;
|
||||
_cleanup_object_unref_ GFile *file = NULL;
|
||||
_cleanup_object_unref_ GFileInputStream *input_stream = NULL;
|
||||
|
||||
/* open the file */
|
||||
rom_fn = fu_device_get_metadata (device, "RomFilename");
|
||||
if (rom_fn == NULL) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"Unable to read firmware from device");
|
||||
return FALSE;
|
||||
}
|
||||
file = g_file_new_for_path (rom_fn);
|
||||
input_stream = g_file_read (file, NULL, error);
|
||||
if (input_stream == NULL)
|
||||
return NULL;
|
||||
|
||||
hash = g_checksum_new (G_CHECKSUM_SHA1);
|
||||
fu_provider_set_status (provider, FWUPD_STATUS_DEVICE_VERIFY);
|
||||
while (TRUE) {
|
||||
gssize sz;
|
||||
sz = g_input_stream_read (G_INPUT_STREAM (input_stream),
|
||||
buffer,
|
||||
sizeof (buffer),
|
||||
NULL, NULL);
|
||||
if (sz <= 0)
|
||||
break;
|
||||
g_checksum_update (hash, buffer, sz);
|
||||
}
|
||||
fu_device_set_metadata (device, FU_DEVICE_KEY_FIRMWARE_HASH,
|
||||
g_checksum_get_string (hash));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_udev_client_add:
|
||||
**/
|
||||
@ -268,6 +314,8 @@ fu_provider_udev_client_add (FuProviderUdev *provider_udev, GUdevDevice *device)
|
||||
if (vendor != NULL)
|
||||
fu_device_set_metadata (dev, FU_DEVICE_KEY_VENDOR, vendor);
|
||||
fu_device_set_metadata (dev, FU_DEVICE_KEY_VERSION, version);
|
||||
if (g_file_test (rom_fn, G_FILE_TEST_EXISTS))
|
||||
fu_device_set_metadata (dev, "RomFilename", rom_fn);
|
||||
|
||||
/* insert to hash */
|
||||
g_hash_table_insert (provider_udev->priv->devices, g_strdup (id), dev);
|
||||
@ -353,6 +401,7 @@ fu_provider_udev_class_init (FuProviderUdevClass *klass)
|
||||
|
||||
provider_class->get_name = fu_provider_udev_get_name;
|
||||
provider_class->coldplug = fu_provider_udev_coldplug;
|
||||
provider_class->verify = fu_provider_udev_verify;
|
||||
object_class->finalize = fu_provider_udev_finalize;
|
||||
|
||||
g_type_class_add_private (klass, sizeof (FuProviderUdevPrivate));
|
||||
|
@ -184,6 +184,21 @@ fu_provider_schedule_update (FuProvider *provider,
|
||||
return fu_provider_offline_setup (error);
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_verify:
|
||||
**/
|
||||
gboolean
|
||||
fu_provider_verify (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
FuProviderVerifyFlags flags,
|
||||
GError **error)
|
||||
{
|
||||
FuProviderClass *klass = FU_PROVIDER_GET_CLASS (provider);
|
||||
if (klass->verify != NULL)
|
||||
return klass->verify (provider, device, flags, error);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_provider_update:
|
||||
**/
|
||||
|
@ -52,6 +52,11 @@ typedef enum {
|
||||
FU_PROVIDER_UPDATE_FLAG_LAST
|
||||
} FuProviderFlags;
|
||||
|
||||
typedef enum {
|
||||
FU_PROVIDER_VERIFY_FLAG_NONE = 0,
|
||||
FU_PROVIDER_VERIFY_FLAG_LAST
|
||||
} FuProviderVerifyFlags;
|
||||
|
||||
struct _FuProviderClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
@ -60,6 +65,10 @@ struct _FuProviderClass
|
||||
const gchar *(*get_name) (FuProvider *provider);
|
||||
gboolean (*coldplug) (FuProvider *provider,
|
||||
GError **error);
|
||||
gboolean (*verify) (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
FuProviderVerifyFlags flags,
|
||||
GError **error);
|
||||
gboolean (*update_online) (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
gint fd,
|
||||
@ -104,6 +113,10 @@ gboolean fu_provider_update (FuProvider *provider,
|
||||
gint fd_fw,
|
||||
FuProviderFlags flags,
|
||||
GError **error);
|
||||
gboolean fu_provider_verify (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
FuProviderVerifyFlags flags,
|
||||
GError **error);
|
||||
gboolean fu_provider_clear_results (FuProvider *provider,
|
||||
FuDevice *device,
|
||||
GError **error);
|
||||
|
120
src/fu-util.c
120
src/fu-util.c
@ -319,6 +319,7 @@ fu_util_get_devices (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
FU_DEVICE_KEY_FLAGS,
|
||||
FU_DEVICE_KEY_TRUSTED,
|
||||
FU_DEVICE_KEY_SIZE,
|
||||
FU_DEVICE_KEY_FIRMWARE_HASH,
|
||||
NULL };
|
||||
const gchar *flags_str[] = {
|
||||
"Internal",
|
||||
@ -887,6 +888,119 @@ fu_util_get_results (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fu_util_verify_internal:
|
||||
**/
|
||||
static gboolean
|
||||
fu_util_verify_internal (FuUtilPrivate *priv, const gchar *id, GError **error)
|
||||
{
|
||||
_cleanup_variant_unref_ GVariant *val = NULL;
|
||||
g_dbus_proxy_call (priv->proxy,
|
||||
"Verify",
|
||||
g_variant_new ("(s)", id),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
fu_util_get_devices_cb, priv);
|
||||
g_main_loop_run (priv->loop);
|
||||
if (priv->val == NULL) {
|
||||
g_dbus_error_strip_remote_error (priv->error);
|
||||
g_propagate_error (error, priv->error);
|
||||
priv->error = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_util_verify_all:
|
||||
**/
|
||||
static gboolean
|
||||
fu_util_verify_all (FuUtilPrivate *priv, GError **error)
|
||||
{
|
||||
AsApp *app;
|
||||
FuDevice *dev;
|
||||
const gchar *tmp;
|
||||
guint i;
|
||||
_cleanup_object_unref_ AsStore *store = NULL;
|
||||
_cleanup_ptrarray_unref_ GPtrArray *devices = NULL;
|
||||
|
||||
/* get devices from daemon */
|
||||
devices = fu_util_get_devices_internal (priv, error);
|
||||
if (devices == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* get results */
|
||||
for (i = 0; i < devices->len; i++) {
|
||||
_cleanup_error_free_ GError *error_local = NULL;
|
||||
dev = g_ptr_array_index (devices, i);
|
||||
if (!fu_util_verify_internal (priv, fu_device_get_id (dev), &error_local)) {
|
||||
g_print ("Failed to verify %s: %s\n",
|
||||
fu_device_get_id (dev),
|
||||
error_local->message);
|
||||
}
|
||||
}
|
||||
|
||||
/* only load firmware from the system */
|
||||
store = as_store_new ();
|
||||
as_store_add_filter (store, AS_ID_KIND_FIRMWARE);
|
||||
if (!as_store_load (store, AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
/* print */
|
||||
for (i = 0; i < devices->len; i++) {
|
||||
const gchar *hash = NULL;
|
||||
const gchar *ver = NULL;
|
||||
_cleanup_free_ gchar *status = NULL;
|
||||
|
||||
dev = g_ptr_array_index (devices, i);
|
||||
hash = fu_device_get_metadata (dev, FU_DEVICE_KEY_FIRMWARE_HASH);
|
||||
if (hash == NULL)
|
||||
continue;
|
||||
app = as_store_get_app_by_id (store, fu_device_get_guid (dev));
|
||||
if (app == NULL) {
|
||||
status = g_strdup ("No metadata");
|
||||
} else {
|
||||
AsRelease *rel;
|
||||
ver = fu_device_get_metadata (dev, FU_DEVICE_KEY_VERSION);
|
||||
rel = as_app_get_release (app, ver);
|
||||
if (rel == NULL) {
|
||||
status = g_strdup_printf ("No version %s", ver);
|
||||
} else {
|
||||
tmp = as_release_get_checksum (rel, G_CHECKSUM_SHA1);
|
||||
if (g_strcmp0 (tmp, hash) != 0) {
|
||||
status = g_strdup_printf ("Failed: for v%s expected %s", ver, tmp);
|
||||
} else {
|
||||
status = g_strdup ("OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
g_print ("%s\t%s\t%s\n", fu_device_get_guid (dev), hash, status);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_util_verify:
|
||||
**/
|
||||
static gboolean
|
||||
fu_util_verify (FuUtilPrivate *priv, gchar **values, GError **error)
|
||||
{
|
||||
if (g_strv_length (values) == 0)
|
||||
return fu_util_verify_all (priv, error);
|
||||
if (g_strv_length (values) != 1) {
|
||||
g_set_error_literal (error,
|
||||
FWUPD_ERROR,
|
||||
FWUPD_ERROR_INTERNAL,
|
||||
"Invalid arguments: expected 'id'");
|
||||
return FALSE;
|
||||
}
|
||||
return fu_util_verify_internal (priv, values[0], error);
|
||||
}
|
||||
|
||||
/**
|
||||
* fu_util_print_data:
|
||||
**/
|
||||
@ -1127,6 +1241,12 @@ main (int argc, char *argv[])
|
||||
/* TRANSLATORS: command description */
|
||||
_("Gets the list of updates for connected hardware"),
|
||||
fu_util_get_updates);
|
||||
fu_util_add (priv->cmd_array,
|
||||
"verify",
|
||||
NULL,
|
||||
/* TRANSLATORS: command description */
|
||||
_("Gets the cryptographic hash of the dumped firmware"),
|
||||
fu_util_verify);
|
||||
fu_util_add (priv->cmd_array,
|
||||
"clear-results",
|
||||
NULL,
|
||||
|
@ -122,6 +122,35 @@
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--***********************************************************-->
|
||||
<method name='Verify'>
|
||||
<doc:doc>
|
||||
<doc:description>
|
||||
<doc:para>
|
||||
Verifies firmware on a device by reading it back and performing
|
||||
a cryptographic hash, typically SHA1.
|
||||
</doc:para>
|
||||
</doc:description>
|
||||
</doc:doc>
|
||||
<arg type='s' name='id' direction='in'>
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
An ID, typically a GUID of the hardware.</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
<arg type='s' name='hash' direction='out'>
|
||||
<doc:doc>
|
||||
<doc:summary>
|
||||
<doc:para>
|
||||
The cryptographic hash of the firmware.
|
||||
</doc:para>
|
||||
</doc:summary>
|
||||
</doc:doc>
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<!--***********************************************************-->
|
||||
<method name='GetResults'>
|
||||
<doc:doc>
|
||||
|
Loading…
Reference in New Issue
Block a user